package com.ship.dispatch.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ship.common.core.constant.ShipApiConstants;
import com.ship.common.core.domain.SpPortData;
import com.ship.common.core.exception.BaseException;
import com.ship.common.core.exception.ServiceException;
import com.ship.common.core.utils.BeanCopyUtils;
import com.ship.common.core.utils.DateUtils;
import com.ship.common.core.utils.OkHttpUtil;
import com.ship.common.core.web.domain.BaseResultCode;
import com.ship.common.core.web.domain.JsonResult;
import com.ship.common.business.service.impl.BaseServiceImpl;
import com.ship.common.security.utils.SecurityUtils;
import com.ship.dispatch.bean.*;
import com.ship.dispatch.bo.*;
import com.ship.dispatch.mapper.*;
import com.ship.dispatch.model.SpYearPlanSchemeModel;
import com.ship.dispatch.model.SpYearlyPlanModel;
import com.ship.dispatch.model.Vo.ShipLoadDaysAndRouteVo;
import com.ship.dispatch.model.Vo.ShipRouteSearchVo;
import com.ship.dispatch.model.dto.SchemeChartSaveDto;
import com.ship.dispatch.model.dto.SchemeChartSimpleDto;
import com.ship.dispatch.service.*;
import com.ship.dispatch.util.CommonUtil;
import com.ship.dispatch.util.IDUtil;
import com.ship.dispatch.vo.*;
import com.ship.system.api.BpmInstanceService;
import com.ship.system.api.model.ProcessInstanceParam;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * <p>
 * 年度计划 服务实现类
 * </p>
 *
 * @author AbyssRabbit@163.com
 * @since 2023-10-17
 */
@Service
public class SpYearlyPlanServiceImpl extends BaseServiceImpl<SpYearlyPlanMapper, SpYearlyPlan> implements SpYearlyPlanService {

    @Resource
    private SpYearlyPlanMapper spYearlyPlanMapper;

    @Resource
    private SpYearPlanSchemeMapper spYearPlanSchemeMapper;

    @Resource
    private SpYearPlanSchemeDetailMapper detailMapper;

    @Autowired
    private SpYearPlanSchemeDetailService spYearPlanSchemeDetailService;

    @Resource
    private ConResourceMemorandumMapper resourceMemorandumMapper;

    @Resource
    private SpDispatchSchemeDetailMapper dispatchDetailMapper;

    @Resource
    private ConShippingMapper conShippingMapper;

    @Resource
    private SpPortInformationMapper portInformationMapper;
    @Autowired
    private SpShipScheduleService shipScheduleService;

    @Resource
    private BpmInstanceService bpmInstanceService;

    @Autowired
    private SpYearPlanCapacityRecordService spYearPlanCapacityRecordService;

    @Autowired
    @Lazy
    private SpInformationService spInformationService;

    private static String shipServerHost;


    private static String shipServerAppKey;

    @Autowired
    private SpPortRouteDistanceService spPortRouteDistanceService;

    @Value("${ship_server.host}")
    public void setShipServerHost(String shipServerHost) {
        this.shipServerHost = shipServerHost;
    }

    @Value("${ship_server.app_key}")
    public void setShipServerAppKey(String shipServerAppKey) {
        this.shipServerAppKey = shipServerAppKey;
    }

    @Value("${file_server.host}")
    private String fileServerHost;

    private BigDecimal ROUND_NUMBER = new BigDecimal("10000");

    @Override
    public JsonResult schemeAdd(SpYearlyPlanModel spYearlyPlanModel) {
        /**
         * 1、判断年度计划及方案信息，不存在则新增
         * 2、获取可用船只信息，校验船只信息，船只排序，有限对非虚拟船进行装载，区分开大船和小船
         * 	如果输入了货量和货数，根据货量及大小船按照最多和最少的区间来判断货数是否匹配，不匹配则终止
         * 3、根据采购合同获取资源方信息，根据资源方获取当前资源方当前年度的船货信息，超出年头年尾则默认年头年尾，没有则补齐
         * 4、遍历资源方的船货信息，获取可插入的装载空挡时间
         * 	计算插入货的信息（装卸载时间等），如果选择了月份，当前转载时间必须在选择的月份内，不满足则往后推时间节点
         * 		遍历可用船只，判断船只在当前时间是否满足，满足则插入，默认满载，扣除货量/货数，如果货量达不到满载则舍去该船货，插入成功，不满足则下一个空挡
         * 5、货数/货量装载完了则新增成功，没有装载完则走虚拟船逻辑
         */

        Date dateNow = new Date();
        //查询最后一条年度计划信息，如果已下发或者为空则增加下一年度的信息，否则就在原计划上面新增
        SpYearlyPlan spYearlyPlan = spYearlyPlanMapper.selectOne(
                new LambdaQueryWrapper<SpYearlyPlan>()
                        .orderByDesc(SpYearlyPlan::getYear)
                        .last("limit 1")
        );
        Long planId;
        Integer year = DateUtil.year(new Date()) + 1;
        if (spYearlyPlan == null) {
            planId = IDUtil.nextId();
            spYearlyPlan = new SpYearlyPlan();
            spYearlyPlan.setId(planId);
            spYearlyPlan.setYear(year);
            spYearlyPlan.setCreateBy(SecurityUtils.getUsername());
            spYearlyPlan.setCreateDate(dateNow);
            spYearlyPlan.setState(1);
            spYearlyPlan.setCreateBy(SecurityUtils.getUsername());
            spYearlyPlanMapper.insert(spYearlyPlan);
        } else {
            if (spYearlyPlan.getState().intValue() == 3) {
                //如果方案已下发，也要新增当前方案下一年度的计划
                planId = IDUtil.nextId();
                year = spYearlyPlan.getYear() + 1;
                spYearlyPlan = new SpYearlyPlan();
                spYearlyPlan.setId(planId);
                spYearlyPlan.setYear(year);
                spYearlyPlan.setCreateBy(SecurityUtils.getUsername());
                spYearlyPlan.setCreateDate(dateNow);
                spYearlyPlan.setState(1);
                spYearlyPlan.setCreateBy(SecurityUtils.getUsername());
                spYearlyPlanMapper.insert(spYearlyPlan);
            }
            planId = spYearlyPlan.getId();

        }
        year = spYearlyPlan.getYear();
        // 根据用户id找出最大的方案号
        SpYearPlanScheme scheme = spYearPlanSchemeMapper.selectOne(
                new LambdaQueryWrapper<SpYearPlanScheme>()
                        .and(wrr -> {
                            wrr.eq(SpYearPlanScheme::getCreateBy, SecurityUtils.getUsername());
                            wrr.or().eq(SpYearPlanScheme::getIsShare,1);
                        })
                        .eq(SpYearPlanScheme::getIsHistory,0)
                        .eq(SpYearPlanScheme::getPlanId,planId)
                        .orderByDesc(SpYearPlanScheme::getCreateDate)
                        .last("limit 1")
        );
        Long schemeId = IDUtil.nextId();
        if (scheme == null) {
            Integer number = 1;
            scheme = new SpYearPlanScheme();
            scheme.setPlanId(planId);
            scheme.setId(schemeId);
            scheme.setNumber(number);
            scheme.setCreateBy(SecurityUtils.getUsername());
            scheme.setCreateDate(dateNow);
            scheme.setIsShare(0);
            scheme.setIsReduce(0);
            scheme.setIsDraft(0);
            scheme.setIsDistribute(0);
            spYearPlanSchemeMapper.insert(scheme);
        } else {
            schemeId = scheme.getId();
        }

        //获取当前可用船只信息（非虚拟船）
        Map<String,List<SpInformation>> shipsMap = spInformationService.getConSpInformation(new Date());
        //可用正常船只
        List<SpInformation> normalShips = shipsMap.get("normalShips");
        //可用的虚拟船只
        List<SpInformation> virtualShips = shipsMap.get("virtualShips");

        if(normalShips.size()+virtualShips.size() == 0){
            //创建虚拟船
            SpInformation virtualShip = spInformationService.createVirtualSpInformation();
            virtualShips.add(virtualShip);
        }
        for (SpYearPlanSchemeModel schemeModel : spYearlyPlanModel.getScheme()) {
            if(schemeModel.getGoodsQuantity() == null && schemeModel.getGoodsNum() == null){
                return JsonResult.failed("货量和货数至少填一个！");
            }

            // 该资源总货量
            if (schemeModel.getGoodsQuantity() != null) {
                //单位为立方米时转为百万英热
                if (schemeModel.getQuantityUnit() != null && schemeModel.getQuantityUnit().equals("2")) {
                    BigDecimal goodsQuantityNew = schemeModel.getGoodsQuantity().multiply(BigDecimal.valueOf(0.985))
                            .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                            .setScale(0, BigDecimal.ROUND_DOWN);
                    schemeModel.setGoodsQuantity(goodsQuantityNew);
                }
                //单位为MT（万吨）转为百万英热
                if (schemeModel.getQuantityUnit() != null && schemeModel.getQuantityUnit().equals("3")) {
                    //1mt = 47.3684mmbtu
                    BigDecimal goodsQuantityNew = schemeModel.getGoodsQuantity().multiply(BigDecimal.valueOf(47.3684))
                            .setScale(0, BigDecimal.ROUND_DOWN);
                    schemeModel.setGoodsQuantity(goodsQuantityNew);
                }
            }
            List<SpInformation> spInformation = new LinkedList<>();

            // 根据条件筛选-判断资源合同中装卸量是否满足船只
            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.selectById(schemeModel.getConId());
            if(resourceMemorandum == null){
                return JsonResult.failed("合同信息异常！");
            }
            //是否是直接使用的虚拟船
            Boolean useVirtualShip = false;
            if(normalShips.size() > 0){
                for (SpInformation ship : normalShips) {
                    // 根据条件筛选-判断船舱容是否为空
                    if (ship.getStorageCapacity() == null) {
                        continue;
                    }
                    // 根据条件筛选-判断IMO
                    if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0) {
                        if(!schemeModel.getImo().contains(ship.getImo())){
                            //IMO不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断船旗国
                    if (schemeModel.getFlagState() != null && schemeModel.getFlagState().size() > 0) {
                        if(!schemeModel.getFlagState().contains(ship.getCountry())){
                            //船旗国不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断油耗
                    if (schemeModel.getOilConsumption() != null) {
                        if (ship.getOilConsumption().compareTo(schemeModel.getOilConsumption()) < 0) {
                            continue;
                        }
                    }

                    BigDecimal mut = ship.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                            .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                            .setScale(0, BigDecimal.ROUND_DOWN);

                    // 根据条件筛选-判断船型起止
                    if (schemeModel.getShipModelStart() != null) {
                        if (schemeModel.getShipModelStart().compareTo(ship.getStorageCapacity()) > 0) {
                            continue;
                        }
                    }
                    if (schemeModel.getShipModelEnd() != null) {
                        if (ship.getStorageCapacity().compareTo(schemeModel.getShipModelStart()) < 0) {
                            continue;
                        }
                    }
                    spInformation.add(ship);
                }
                if(spInformation.size() == 0){
                    //所有船只都不符合条件则选择虚拟船来进行分配
                    useVirtualShip = true;
                    spInformation.addAll(virtualShips);
                }
            }else{
                useVirtualShip = true;
                spInformation.addAll(virtualShips);
            }
            // 处理大船和小船
            List<SpInformation> maxShips = new ArrayList<>();
            List<SpInformation> minShips = new ArrayList<>();
            for (SpInformation sp : spInformation) {
                if (sp.getStorageCapacity().compareTo(new BigDecimal(160000)) > 0) {
                    maxShips.add(sp);
                } else {
                    minShips.add(sp);
                }
            }
            //大小船根据舱容来排序（倒序）
            maxShips = maxShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());
            minShips = minShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());

//            if(schemeModel.getGoodsQuantity() != null && schemeModel.getGoodsNum() != null){
//                //货量和货数都填了的情况下需要校验是否匹配（货量/最大舱容 <= 货数 <= 货量/最小舱容）
//                BigDecimal maxCapacity = maxShips.size() > 0 ? maxShips.get(0).getStorageCapacity() : minShips.get(0).getStorageCapacity();
//                BigDecimal minCapacity = minShips.size() > 0 ? minShips.get(minShips.size()-1).getStorageCapacity() : maxShips.get(maxShips.size()-1).getStorageCapacity();
//                int maxCount = schemeModel.getGoodsQuantity().divide(minCapacity,0, RoundingMode.HALF_UP).intValue();
//                int minCount = schemeModel.getGoodsQuantity().divide(maxCapacity,0, RoundingMode.HALF_UP).intValue();
//                if(schemeModel.getGoodsNum() < minCount || schemeModel.getGoodsNum() > maxCount ){
//                    return JsonResult.failed("货量【"+schemeModel.getGoodsQuantity()+"】和货数【"+schemeModel.getGoodsNum()+"】不匹配！");
//                }
//            }

            BigDecimal allGoodsQuantity = schemeModel.getGoodsQuantity();

            //剩余货量
            BigDecimal goodsQuantity = schemeModel.getGoodsQuantity();
            //剩余货数
            Integer goodsNumber = schemeModel.getGoodsNum();

            //根据资源方获取当前年度内排货的装载时间信息（正序）
            List<Date> loadDateList = getSchemeDetailsBySellerAndYear(schemeModel.getLoadMonth(),schemeId,resourceMemorandum.getSeller(),year);
            if(loadDateList.size() == 0){
                return JsonResult.failed("当前年度该资源方【"+resourceMemorandum.getSeller()+"】已无可装载的时间空挡！");
            }

            //船只时间不符合的可用装载时间点
            List<Date> unUseLoadTime = new LinkedList<>();
            ShipLoadDaysAndRouteVo loadDays = spPortRouteDistanceService.getCommonLoadDay(schemeModel.getLoadingSort()
                    ,schemeModel.getUnloadSort(),schemeModel.getSpeed(),schemeModel.getPassStrait(),schemeModel.getPassArea());
            if(loadDays == null){
                return JsonResult.failed("当前航线暂无可匹配的路线信息！");
            }
            for (Date loadDate : loadDateList) {
                //根据装载时间初步生成船货信息

                if (goodsQuantity != null) {
                    if(goodsQuantity.compareTo(BigDecimal.ZERO) <= 0){
                        //剩余货量为0，插入结束
                        break;
                    }
                } else {
                    if(goodsNumber <= 0){
                        //剩余货量为0，插入结束
                        break;
                    }
                }

                SpYearPlanSchemeDetail schemeDetail = initPlanSchemeDetail(year,resourceMemorandum,schemeId,loadDate,schemeModel,loadDays);
                schemeDetail.setPlanId(planId);

                int month = DateUtil.month(loadDate) + 1;
                SpInformation loadShipInfo = null;
                //根据装载月份来判断大小船的优先级
                if (month >= 4 && month <= 10) {
                    //优先大船
                    if(maxShips.size() > 0){
                        for (SpInformation ship : maxShips) {
                            if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0
                                    && !schemeModel.getImo().contains(ship.getImo())) {
                                unUseLoadTime.add(loadDate);
                                continue;
                            }
                            if(checkShipTimeWindows(ship,schemeDetail)){
                                loadShipInfo = ship;
                                break;
                            }
                        }
                    }
                    if(loadShipInfo == null && minShips.size() > 0){
                        //大船不符合，则派小船
                        for (SpInformation ship : minShips) {
                            if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                    && !schemeModel.getImo().contains(ship.getImo())) {
                                unUseLoadTime.add(loadDate);
                                continue;
                            }
                            if(checkShipTimeWindows(ship,schemeDetail)){
                                loadShipInfo = ship;
                                break;
                            }
                        }
                    }
                }else{
                    //优先小船
                    if(minShips.size() > 0){
                        for (SpInformation ship : minShips) {
                            if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                    && !schemeModel.getImo().contains(ship.getImo())) {
                                unUseLoadTime.add(loadDate);
                                continue;
                            }
                            if(checkShipTimeWindows(ship,schemeDetail)){
                                loadShipInfo = ship;
                                break;
                            }
                        }
                    }
                    if(loadShipInfo == null && maxShips.size() > 0){
                        //小船都不符合，则派大船
                        for (SpInformation ship : maxShips) {
                            if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                    && !schemeModel.getImo().contains(ship.getImo())) {
                                unUseLoadTime.add(loadDate);
                                continue;
                            }
                            if(checkShipTimeWindows(ship,schemeDetail)){
                                loadShipInfo = ship;
                                break;
                            }
                        }
                    }
                }

                if(loadShipInfo == null){
                    //说明现有船只都不满足，记录未新增的时间点，继续下一时间点
                    unUseLoadTime.add(loadDate);
                    continue;
                }

                // 本艘船能够装载的货量（百万英热），排货默认满载
                BigDecimal mut = loadShipInfo.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                        .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                        .setScale(0, BigDecimal.ROUND_DOWN);

                if (goodsQuantity != null) {
                    if(allGoodsQuantity.compareTo(mut) > 0){
                        if(goodsQuantity.compareTo(mut) < 0){
                            //如果剩余货量达不到当前船只满载，则舍去这船货
                            goodsQuantity = BigDecimal.ZERO;
                            break;
                        }
                    }else{
                        //输入货量小于舱容，则说明只拉一船货，装载量=货量
                        mut = goodsQuantity;
                    }
                    goodsQuantity = goodsQuantity.subtract(mut);
                } else {
                    if(goodsNumber == 0){
                        //货数已拍完
                        goodsNumber = 0;
                        break;
                    }
                    goodsNumber = (goodsNumber -1);
                }
                mut = mut.divide(ROUND_NUMBER,0,RoundingMode.DOWN).multiply(new BigDecimal("10000"));

                schemeDetail.setShipName(loadShipInfo.getNameOfVessel());
                schemeDetail.setImo(loadShipInfo.getImo());
                schemeDetail.setSingleGoodsQuantity(mut);
                schemeDetail.setLoadingCapacity(mut);
                schemeDetail.setLoadingCapacityUnit(1);
                schemeDetail.setStorageCapacity(loadShipInfo.getStorageCapacity());
                schemeDetail.setOilConsumption(loadShipInfo.getOilConsumption());
//                schemeDetail.setRentTimeSlot(loadShipInfo.getRentTimeSlot());

                //计算时间空挡天数以及贸易利润率信息
                getIntervalDaysAndTradeRate(schemeDetail,loadShipInfo,resourceMemorandum);

                spYearPlanSchemeDetailService.getBaseMapper().insert(schemeDetail);
                updateShipSchedule(schemeDetail,loadShipInfo);
            }


            if (goodsQuantity != null) {
                if(goodsQuantity.compareTo(BigDecimal.ZERO) > 0){
                    //所有时间派完了都还有剩余，说明船只不够，使用虚拟船拉，如果现有虚拟船已用完，则新增虚拟船
                    if(!useVirtualShip && virtualShips.size() > 0){
                        virtualShipDispatch(unUseLoadTime,virtualShips,schemeModel
                                ,goodsQuantity,goodsNumber,planId, year, resourceMemorandum, schemeId, false,loadDays);
                    }else{
                        List<SpInformation> ships = new LinkedList<>();
                        SpInformation virtualShip = spInformationService.createVirtualSpInformation();
                        ships.add(virtualShip);
                        virtualShipDispatch(unUseLoadTime,ships,schemeModel
                                ,goodsQuantity,goodsNumber,planId, year, resourceMemorandum, schemeId, false,loadDays);
                    }
                }
            } else {
                if(goodsNumber > 0){
                    //所有时间派完了都还有剩余，说明船只不够，使用虚拟船拉获取现新增虚拟船
                    if(!useVirtualShip && virtualShips.size() > 0){
                        virtualShipDispatch(unUseLoadTime,virtualShips,schemeModel
                                ,goodsQuantity,goodsNumber,planId, year, resourceMemorandum, schemeId, false,loadDays);
                    }else{
                        List<SpInformation> ships = new LinkedList<>();
                        SpInformation virtualShip = spInformationService.createVirtualSpInformation();
                        ships.add(virtualShip);
                        virtualShipDispatch(unUseLoadTime,ships,schemeModel
                                ,goodsQuantity,goodsNumber,planId, year, resourceMemorandum, schemeId, false,loadDays);
                    }
                }
            }
            //记录资源方装载货量信息
            saveSellerCapacityRecord(planId,year,resourceMemorandum);
        }
        List<SpYearPlanSchemeDetail> yearPlanSchemeDetails = detailMapper.selectList(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>().eq(SpYearPlanSchemeDetail::getSchemeId, schemeId));
        if (yearPlanSchemeDetails.size() > 0) {
            yearPlanSchemeDetails = CommonUtil.resetVoyageNoAndGoodsNumber(yearPlanSchemeDetails);

            //重新计算空载时间
            yearPlanSchemeDetails = resetSetLastEmptyInfo(yearPlanSchemeDetails);

            //重新计算一下前后空挡信息
            yearPlanSchemeDetails = getIntervalDays(yearPlanSchemeDetails,spYearlyPlan.getYear());

            //计算优化成本及船舶利用率
            yearPlanSchemeDetails = getUseRateAndDispatchCost(yearPlanSchemeDetails,spYearlyPlan.getYear());

            spYearPlanSchemeDetailService.updateBatchById(yearPlanSchemeDetails);
        }
        return JsonResult.success(String.valueOf(spYearlyPlan.getId()));
    }




    /**
     * 重新计算上一航次信息
     * @param detailList
     * @return
     */
    public List<SpYearPlanSchemeDetail> resetSetLastEmptyInfo(List<SpYearPlanSchemeDetail> detailList){
        if(detailList.size() == 0){
            return detailList;
        }
        for (SpYearPlanSchemeDetail schemeDetail : detailList) {
            //根据船只上一航次卸载港-当前航次装载港计算空载时间
            //预计空载航程天数
            SpYearPlanSchemeDetail lastSchemeDetail = spYearPlanSchemeDetailService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                            .lt(SpYearPlanSchemeDetail::getExpectedLoadingDate, DateUtils.parseDateToStr("yyyy-MM-dd",schemeDetail.getExpectedLoadingDate()))
                            .eq(SpYearPlanSchemeDetail::getSchemeId,detailList.get(0).getSchemeId())
                            .eq(SpYearPlanSchemeDetail::getImo,schemeDetail.getImo())
                            .orderByDesc(SpYearPlanSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            schemeDetail.setLastShipSpeed(schemeDetail.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                
                //没有空载时间则清空空载航线信息
                schemeDetail.setEmptyDistance(0);
                schemeDetail.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,schemeDetail.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    schemeDetail.setEmptyDistance(routeDay.getTrackDistance());
                    schemeDetail.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    schemeDetail.setExpectedEmptyLoadVoyageDays(day);
                    schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                    schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }
        }
        return detailList;
    }

    /**
     * 获取船只租期
     * @param imo
     * @return
     */
    public ConShipping getShipRentTime(String imo){
        ConShipping conShipping = conShippingMapper.selectOne(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,imo)
                        .in(ConShipping::getContractType,1,2)
                        .in(ConShipping::getStatus, 1, 2, 3)
                        .orderByDesc(ConShipping::getDeliveryDateBegin)
                        .last("limit 1")
        );
        return conShipping;
    }

    /**
     * 获取船货前后空挡天数
     * @param detailAllList
     * @return
     */
    public List<SpYearPlanSchemeDetail> getUseRateAndDispatchCost(List<SpYearPlanSchemeDetail> detailAllList, int year){
        List<SpInformation> spInformations = spInformationService.getAllSpInformationUsed(null);
        if(spInformations.size() == 0){
            return detailAllList;
        }
        if(detailAllList.size() == 0){
            return detailAllList;
        }
        Map<String,List<SpInformation>> shipMap = spInformations.stream().collect(Collectors.groupingBy(SpInformation::getImo));
        Map<String,List<SpYearPlanSchemeDetail>> schemeDetailMap = detailAllList.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getImo));

        List<SpYearPlanSchemeDetail> resultList = new LinkedList<>();
        for (String s : schemeDetailMap.keySet()) {
            List<SpYearPlanSchemeDetail> detailList = schemeDetailMap.get(s);
            if(shipMap.get(s) == null){
                resultList.addAll(detailList);
                continue;
            }
            SpInformation spInformation = shipMap.get(s).get(0);
            BigDecimal yearInRentDays = BigDecimal.ZERO;
            BigDecimal yearInRentMoney = BigDecimal.ZERO;
            BigDecimal yearOutRentDays = BigDecimal.ZERO;
            BigDecimal yearOutRentMoney = BigDecimal.ZERO;

            ShipRentYearInfoBO yearInfoBO = CommonUtil.getShipYearTradeInfo(spInformation,year);
            yearInRentDays = yearInRentDays.add(yearInfoBO.getYearInRentDays());
            yearInRentMoney = yearInRentMoney.add(yearInfoBO.getYearInRentMoney());
            yearOutRentDays = yearOutRentDays.add(yearInfoBO.getYearOutRentDays());
            yearOutRentMoney = yearOutRentMoney.add(yearInfoBO.getYearOutRentMoney());

            BigDecimal shipDays = BigDecimal.ZERO;
            for (SpYearPlanSchemeDetail schemeDetail : detailList) {
                BigDecimal currentDay = BigDecimal.ZERO;
                if(schemeDetail.getExpectedFullLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedFullLoadVoyageDays())));
                }
                if(schemeDetail.getExpectedEmptyLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedEmptyLoadVoyageDays())));
                }
                shipDays = shipDays.add(currentDay);

                if(yearInRentDays.compareTo(BigDecimal.ZERO) != 0) {
                    schemeDetail.setShipUseRate(currentDay.divide(yearInRentDays, 3, RoundingMode.HALF_UP).multiply(new BigDecimal("100")));
                }
            }

            //计算船舶优化成本=外租收益-运力成本
            //外租收益=所有租出船舶租金*该船舶本年度合同租出天数之和
            //运力成本=所有船舶的租金*年度租的天数-租出的船舶天数*所有船舶租出的租金
            BigDecimal dispatchCost = yearOutRentMoney.subtract(yearInRentMoney.subtract(yearOutRentMoney));
            dispatchCost = dispatchCost.divide(new BigDecimal("10000"),0,RoundingMode.UP);
            for (SpYearPlanSchemeDetail schemeDetail : detailList) {
                schemeDetail.setDispatchCost(dispatchCost);

            }
            resultList.addAll(detailList);
        }
        return resultList;
    }

    /**
     * 获取船货前后空挡天数
     * @param detailAllList
     * @param year
     * @return
     */
    public List<SpYearPlanSchemeDetail> getIntervalDays(List<SpYearPlanSchemeDetail> detailAllList, int year){
        if(detailAllList.size() == 0){
            return detailAllList;
        }
        Date yearBegin = DateUtil.parse(year + "-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss");
        Date yearEnd = DateUtil.parse(year + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss");
        List<SpYearPlanSchemeDetail> saveList = new LinkedList<>();
        Map<String,List<SpYearPlanSchemeDetail>> detailMap = detailAllList.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getImo));
        for (String s : detailMap.keySet()) {
            List<SpYearPlanSchemeDetail> detailList = detailMap.get(s);
//            ConShipping conShipping = getShipRentTime(s);
//            if(conShipping != null){
//                if(conShipping.getDeliveryDateBegin().after(yearBegin) && conShipping.getDeliveryDateBegin().before(yearEnd)){
//                    yearBegin = conShipping.getDeliveryDateBegin();
//                }
//                if(conShipping.getRedeliveryDateEnd().before(yearEnd) && conShipping.getRedeliveryDateEnd().after(yearBegin)){
//                    yearEnd = conShipping.getRedeliveryDateEnd();
//                }
//            }
            if(detailList.size() == 1){
                Integer frontDays = 0;
                Integer backDays = 0;
                Integer emptyDays = 0;
                SpYearPlanSchemeDetail schemeDetail = detailList.get(0);
                if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                    frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                }
                if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                    backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                }
                schemeDetail.setFrontDays(frontDays);
                schemeDetail.setBackDays(backDays);
                if(frontDays >= 30){
                    emptyDays = (emptyDays+frontDays);
                }
                if(backDays >= 30){
                    emptyDays = (emptyDays+backDays);
                }
                schemeDetail.setEmptyDays(emptyDays);
                schemeDetail.setUnoccupiedDays(emptyDays);
                saveList.addAll(detailList);
                continue;
            }
            detailList = detailList.stream().sorted(Comparator.comparing(SpYearPlanSchemeDetail::getExpectedEmptyReturnWindow))
                    .collect(Collectors.toList());

            Date lastEndDate = yearBegin;
            for (int i = 0; i < detailList.size(); i++) {
                SpYearPlanSchemeDetail schemeDetail = detailList.get(i);
                Integer frontDays = 0;
                Integer backDays = 0;
                Integer emptyDays = 0;
                if(i == (detailList.size()-1)){
                    //最后一条
                    frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                        backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                    }
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    continue;
                }

                SpYearPlanSchemeDetail nextSchemeDetail = detailList.get(i+1);

                if(i == 0){
                    //第一条
                    if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                        frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    }
                    backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                            ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }

                    lastEndDate = schemeDetail.getExpectedUnloadingWindow();
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    continue;
                }
                frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                        ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                schemeDetail.setFrontDays(frontDays);
                schemeDetail.setBackDays(backDays);
                if(frontDays >= 30){
                    emptyDays = (emptyDays+frontDays);
                }
                if(backDays >= 30){
                    emptyDays = (emptyDays+backDays);
                }
                schemeDetail.setEmptyDays(emptyDays);
                schemeDetail.setUnoccupiedDays(emptyDays);
                lastEndDate = schemeDetail.getExpectedUnloadingWindow();
            }
            saveList.addAll(detailList);
        }

        return saveList;
    }

    @Override
    public JsonResult getSchemeHistory(HistoryDispatchBO historyDispatchBO) {
        List<SpYearPlanScheme> planSchemes = new LinkedList<>();
        if(StringUtils.isBlank(historyDispatchBO.getPlanId())){
            return JsonResult.success(planSchemes);
        }
        SpYearlyPlan plan = spYearlyPlanMapper.selectById(historyDispatchBO.getPlanId());
        if(plan == null){
            return JsonResult.success(planSchemes);
        }
        planSchemes = spYearPlanSchemeMapper.selectList(
                new LambdaQueryWrapper<SpYearPlanScheme>()
                        .eq(SpYearPlanScheme::getPlanId,historyDispatchBO.getPlanId())
                        .eq(StringUtils.isBlank(historyDispatchBO.getCreateBy()),SpYearPlanScheme::getIsShare,1)
                        .eq(StringUtils.isNoneBlank(historyDispatchBO.getCreateBy()),SpYearPlanScheme::getCreateBy,historyDispatchBO.getCreateBy())
                        .eq(StringUtils.isNoneBlank(historyDispatchBO.getCreateBy()),SpYearPlanScheme::getIsShare,"0")
                        .orderByDesc(SpYearPlanScheme::getCreateDate)
        );

        if(planSchemes.size() == 0){
            return JsonResult.success(new LinkedList<>());
        }
        //获取船舶信息
        List<SpInformation> spInformations = spInformationService.getAllSpInformationUsed(null);
        if(spInformations.size() == 0){
            return JsonResult.failed("暂无可用船舶信息！");
        }

        BigDecimal yearInRentDays = BigDecimal.ZERO;
        BigDecimal yearInRentMoney = BigDecimal.ZERO;
        BigDecimal yearOutRentDays = BigDecimal.ZERO;
        BigDecimal yearOutRentMoney = BigDecimal.ZERO;
        for (SpInformation spInformation : spInformations) {
            ShipRentYearInfoBO yearInfoBO = CommonUtil.getShipYearTradeInfo(spInformation,plan.getYear());
            yearInRentDays = yearInRentDays.add(yearInfoBO.getYearInRentDays());
            yearInRentMoney = yearInRentMoney.add(yearInfoBO.getYearInRentMoney());
            yearOutRentDays = yearOutRentDays.add(yearInfoBO.getYearOutRentDays());
            yearOutRentMoney = yearOutRentMoney.add(yearInfoBO.getYearOutRentMoney());
        }

        List<JSONObject> resultList = new LinkedList<>();
        for (SpYearPlanScheme spYearPlanScheme : planSchemes) {
            //获取年度计划方案详情信息
            List<SpYearPlanSchemeDetail> details = spYearPlanSchemeDetailService.getBaseMapper().selectList(
                    new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                            .eq(SpYearPlanSchemeDetail::getSchemeId,spYearPlanScheme.getId())
            );
            if(details.size() == 0){
                continue;
            }
            int year = DateUtil.year(details.get(0).getExpectedLoadingDate());
            JSONObject resultData = new JSONObject();

            // 获取第一艘船装载时间和卸载时间
            SpYearPlanSchemeDetail detailFirst = details.stream().min(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
            resultData.put("firstShipLoadingTime",detailFirst.getExpectedLoadingDate().getTime());

            resultData.put("schemeInfo",spYearPlanScheme);
            //船舶排布
            Map<String,List<SpYearPlanSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipName));
            JSONArray boatsArray = new JSONArray();
            int emptyDays = 0;
            BigDecimal shipDays = BigDecimal.ZERO;
            BigDecimal tradingProfit = BigDecimal.ZERO;
            BigDecimal dispatchCost = BigDecimal.ZERO;
            for (SpInformation spInformation : spInformations) {
                String boatName = spInformation.getNameOfVessel();
                JSONObject boatDetailJson = new JSONObject();
                List<SpYearPlanSchemeDetail> boatDetails = boatMap.get(boatName) != null ? boatMap.get(boatName) : new LinkedList<>();
                if(boatDetails.size() > 0 && boatDetails.get(0).getDispatchCost() != null){
                    dispatchCost = dispatchCost.add(boatDetails.get(0).getDispatchCost());
                }
                for (int i = 0; i < boatDetails.size(); i++) {
                    SpYearPlanSchemeDetail boatDetail = boatDetails.get(i);
                    if(boatDetail.getExpectedFullLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedFullLoadVoyageDays())));
                    }
                    if(boatDetail.getExpectedEmptyLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedEmptyLoadVoyageDays())));
                    }
                    if(boatDetail.getTradingProfit() != null){
                        tradingProfit = tradingProfit.add(boatDetail.getTradingProfit());
                    }

                    if(boatDetail.getFrontDays() != null && boatDetail.getFrontDays() > 30){
                        emptyDays = (emptyDays + boatDetail.getFrontDays());
                    }
                    if(i == (boatDetails.size() - 1)){
                        if(boatDetail.getBackDays() != null && boatDetail.getBackDays() > 30){
                            emptyDays = (emptyDays + boatDetail.getBackDays());
                        }
                    }
                }

                if(boatDetails.size() == 0 && boatName.indexOf("虚拟") > -1){
                    continue;
                }

                boatDetailJson.put("rentTimeSlot",JSONArray.parseArray(spInformation.getRentTimeSlot()));
                boatDetailJson.put("shipName",boatName);
                boatDetailJson.put("details",boatMap.get(boatName) != null ? boatMap.get(boatName) : new LinkedList<>());
                boatsArray.add(boatDetailJson);
            }

            //船舶利用率=（总的航行天数（空载+满载）+租出的天数）/年度租入天数
            shipDays = shipDays.add(yearOutRentDays);

            BigDecimal shipUseRate = shipDays.multiply(new BigDecimal("100")).divide(yearInRentDays, 2, RoundingMode.HALF_UP);

            resultData.put("shipUseRate",shipUseRate);
            resultData.put("dispatchCost",dispatchCost);
            resultData.put("unoccupiedDays",emptyDays);
            resultData.put("shipNames",boatsArray);
            resultData.put("tradingProfit",tradingProfit);

            //资源排布
            Map<String,List<SpYearPlanSchemeDetail>> goodsMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getGoodsNum));
            JSONArray goodsArray = new JSONArray();
            for (String goodsName : goodsMap.keySet()) {
                JSONObject goodsDetailJson = new JSONObject();
                goodsDetailJson.put("goodsName",goodsName);
                goodsDetailJson.put("details",goodsMap.get(goodsName));
                goodsArray.add(goodsDetailJson);
            }
            resultData.put("goodsNames",goodsArray);
            //资源方排布
            Map<String,List<SpYearPlanSchemeDetail>> sourceSideMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getSeller));
            JSONArray sourceSideArray = new JSONArray();
            for (String sourceSideName : sourceSideMap.keySet()) {
                JSONObject sourceSideDetailJson = new JSONObject();
                List<SpYearPlanSchemeDetail> detailList = sourceSideMap.get(sourceSideName);
                sourceSideDetailJson.put("sellerName",sourceSideName);
                sourceSideDetailJson.put("details",detailList);
                sourceSideArray.add(sourceSideDetailJson);
            }
            resultData.put("sellerNames",sourceSideArray);
            resultData.put("schemeId",spYearPlanScheme.getId());
            resultList.add(resultData);
        }

        return JsonResult.success(resultList);
    }

    @Override
    public List<ConResourceSellerVO> selectAllSellersByScheme(DispatchSellerBo sellerBo) {
        List<ConResourceSellerVO> resultData = this.baseMapper.selectAllSellersByScheme(sellerBo.getSchemeId());
        return resultData;
    }

    @Override
    public JsonResult getPlanSchemeChart(SchemeChartSimpleDto dto) {
        Integer year = null;
        int planState = 1;
        BigDecimal shipUseRate = BigDecimal.ZERO;
        BigDecimal dispatchCost = BigDecimal.ZERO;

        JSONObject emptyResultData = new JSONObject();
        JSONArray emptyBoatsArray = new JSONArray();

        emptyResultData.put("tradingProfit",0.00);
        emptyResultData.put("unoccupiedDays",0);
        emptyResultData.put("shipUseRate",shipUseRate);
        emptyResultData.put("dispatchCost",dispatchCost);
        emptyResultData.put("planId",dto.getPlanId());
        emptyResultData.put("goodsNames",new JSONArray());
        emptyResultData.put("sellerNames",new JSONArray());

        Boolean isAdd = false;
        if(StringUtils.isBlank(dto.getPlanId())){
            isAdd = true;
            //说明是新增，通过最后一条年度计划来判断需要新增那一年的，如果没有数据则默认生成下一年度的数据
            SpYearlyPlan yearlyPlan = spYearlyPlanMapper.selectOne(
                    new LambdaQueryWrapper<SpYearlyPlan>()
                            .orderByDesc(SpYearlyPlan::getYear)
                            .last("limit 1")
            );
            if(yearlyPlan == null){
                //没有年度计划数据则直接展示下一年度信息，时间轴为下一年头
                emptyResultData.put("firstShipLoadingTime",com.ship.common.core.utils.DateUtil.String2Date((LocalDate.now().getYear()+1)+"-01-01 00:00:00","yyyy-MM-dd").getTime());
                return JsonResult.success(emptyResultData);
            }
            year = yearlyPlan.getYear();
            dto.setPlanId(yearlyPlan.getId().toString());
            planState = yearlyPlan.getState();
        }else{
            SpYearlyPlan currentYearlyPlan = spYearlyPlanMapper.selectById(dto.getPlanId());
            if(currentYearlyPlan == null){
                return JsonResult.failed("年度计划ID异常");
            }
            year = currentYearlyPlan.getYear();
            planState = currentYearlyPlan.getState();
        }

        List<SpInformation> spInformations = spInformationService.getAllSpInformationUsed(year);
        if(spInformations.size() == 0){
            return JsonResult.success(emptyResultData);
        }
        for (SpInformation spInformation : spInformations) {
            String boatName = spInformation.getNameOfVessel();
            if(boatName.indexOf("虚拟") > -1){
                continue;
            }
            JSONObject boatDetailJson = new JSONObject();
            boatDetailJson.put("shipName",boatName);
            boatDetailJson.put("storageCapacity",spInformation.getStorageCapacity());
            boatDetailJson.put("storageCapacityMmbtu",spInformation.getStorageCapacityMmbtu());
            boatDetailJson.put("imo",spInformation.getImo());
            boatDetailJson.put("rentTimeSlot",JSONArray.parseArray(spInformation.getRentTimeSlot()));
            boatDetailJson.put("details",new LinkedList<>());
            emptyBoatsArray.add(boatDetailJson);
        }
        emptyResultData.put("shipNames",emptyBoatsArray);

        //获取最新的一条被年度计划方案信息
        SpYearPlanScheme spYearPlanScheme = spYearPlanSchemeMapper.selectOne(
                new LambdaQueryWrapper<SpYearPlanScheme>()
                        .eq(SpYearPlanScheme::getPlanId,dto.getPlanId())
                        .eq(SpYearPlanScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpYearPlanScheme::getCreateBy,SecurityUtils.getUsername());
                            qw.or().eq(SpYearPlanScheme::getIsShare,1);
                        })
                        .orderByDesc(SpYearPlanScheme::getCreateDate)
                        .last("limit 1")
        );
        if(spYearPlanScheme == null){
            //没有年度计划,返回空数据渲染甘特图
            emptyResultData.put("firstShipLoadingTime",com.ship.common.core.utils.DateUtil.String2Date(year+"-01-01 00:00:00","yyyy-MM-dd").getTime());
            return JsonResult.success(emptyResultData);
        }
        int emptyDays = 0;
        //贸易利润率
        BigDecimal tradingProfit = BigDecimal.ZERO;

        BigDecimal yearInRentDays = BigDecimal.ZERO;
        BigDecimal yearInRentMoney = BigDecimal.ZERO;
        BigDecimal yearOutRentDays = BigDecimal.ZERO;
        BigDecimal yearOutRentMoney = BigDecimal.ZERO;
        for (SpInformation spInformation : spInformations) {
            ShipRentYearInfoBO yearInfoBO = CommonUtil.getShipYearTradeInfo(spInformation,year);
            yearInRentDays = yearInRentDays.add(yearInfoBO.getYearInRentDays());
            yearInRentMoney = yearInRentMoney.add(yearInfoBO.getYearInRentMoney());
            yearOutRentDays = yearOutRentDays.add(yearInfoBO.getYearOutRentDays());
            yearOutRentMoney = yearOutRentMoney.add(yearInfoBO.getYearOutRentMoney());
        }

        if(isAdd){
            //如果是新增，则查询当前年度是否有跨到下一年的数据，有则以跨年的装载时间为开始时间轴，没有则以下一年头为开始时间轴
            List<SpYearPlanSchemeDetail> lastDetails = spYearPlanSchemeDetailService.getBaseMapper().selectList(
                    new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                            .eq(SpYearPlanSchemeDetail::getSchemeId,spYearPlanScheme.getId())
                            .ge(SpYearPlanSchemeDetail::getExpectedEmptyReturnWindow,(year+1)+"-01-01")
            );
            if(lastDetails.size() == 0){
                emptyResultData.put("firstShipLoadingTime",com.ship.common.core.utils.DateUtil.String2Date((year+1)+"-01-01 00:00:00","yyyy-MM-dd").getTime());
                return JsonResult.success(emptyResultData);
            }
            lastDetails = lastDetails.stream().sorted(Comparator.comparing(SpYearPlanSchemeDetail::getSchemeNumber)).collect(Collectors.toList());

            for (SpYearPlanSchemeDetail detail : lastDetails) {
                if(detail.getExpectedLoadingPortId() != null){
                    List<String> portIds = new LinkedList<>();
                    portIds.add(String.valueOf(detail.getExpectedLoadingPortId()));
                    detail.setExpectedLoadingPorts(portIds);
                }
                if(detail.getExpectedUnloadingPortId() != null){
                    List<String> portIds = new LinkedList<>();
                    portIds.add(String.valueOf(detail.getExpectedUnloadingPortId()));
                    detail.setExpectedUnloadingPorts(portIds);
                }
            }

            // 获取第一艘船装载时间和卸载时间
            SpYearPlanSchemeDetail detailFirst = lastDetails.stream().max(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
            emptyResultData.put("firstShipLoadingTime",DateUtil.offsetMonth(detailFirst.getExpectedLoadingDate(),-2).getTime());

            //船舶排布
            Map<String,List<SpYearPlanSchemeDetail>> boatMap = lastDetails.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipName));

            BigDecimal shipDays = BigDecimal.ZERO;
            JSONArray boatsArray = new JSONArray();
            for (SpInformation spInformation : spInformations) {
                String boatName = spInformation.getNameOfVessel();
                JSONObject boatDetailJson = new JSONObject();
                List<SpYearPlanSchemeDetail> boatDetails = boatMap.get(boatName) != null ? boatMap.get(boatName) : new LinkedList<>();

                if(boatDetails.size() > 0 && boatDetails.get(0).getDispatchCost() != null){
                    dispatchCost = dispatchCost.add(boatDetails.get(0).getDispatchCost());
                }
                for (int i = 0; i < boatDetails.size(); i++) {
                    SpYearPlanSchemeDetail boatDetail = boatDetails.get(i);
                    if(boatDetail.getExpectedFullLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedFullLoadVoyageDays())));
                    }
                    if(boatDetail.getExpectedEmptyLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedEmptyLoadVoyageDays())));
                    }
                    if(boatDetail.getTradingProfit() != null){
                        tradingProfit = tradingProfit.add(boatDetail.getTradingProfit());
                    }
                    if(boatDetail.getFrontDays() != null && boatDetail.getFrontDays() > 30){
                        emptyDays = (emptyDays + boatDetail.getFrontDays());
                    }
                    if(i == (boatDetails.size() - 1)){
                        if(boatDetail.getBackDays() != null && boatDetail.getBackDays() > 30){
                            emptyDays = (emptyDays + boatDetail.getBackDays());
                        }
                    }
                }
                if(boatDetails.size() == 0 && boatName.indexOf("虚拟") > -1){
                    continue;
                }

                boatDetailJson.put("imo",spInformation.getImo());
                boatDetailJson.put("storageCapacity",spInformation.getStorageCapacity());
                boatDetailJson.put("storageCapacityMmbtu",spInformation.getStorageCapacityMmbtu());
                boatDetailJson.put("rentTimeSlot",JSONArray.parseArray(spInformation.getRentTimeSlot()));
                boatDetailJson.put("shipName",boatName);
                boatDetailJson.put("details",boatDetails);
                boatsArray.add(boatDetailJson);
            }

            //船舶利用率=（总的航行天数（空载+满载）+租出的天数）/年度租入天数
            shipDays = shipDays.add(yearOutRentDays);

            shipUseRate = shipDays.multiply(new BigDecimal("100")).divide(yearInRentDays, 2, RoundingMode.HALF_UP);

            emptyResultData.put("shipUseRate",shipUseRate);
            emptyResultData.put("dispatchCost",dispatchCost);
            emptyResultData.put("shipNames",boatsArray);
            emptyResultData.put("unoccupiedDays",emptyDays);
            emptyResultData.put("tradingProfit",tradingProfit);
            return JsonResult.success(emptyResultData);
        }

        //获取年度计划方案详情信息
        List<SpYearPlanSchemeDetail> details = spYearPlanSchemeDetailService.getBaseMapper().selectList(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .eq(SpYearPlanSchemeDetail::getSchemeId,spYearPlanScheme.getId())
        );

        //获取当前年度计划上一年度的方案信息，防止上一年度有跨年到当前年度的情况
        SpYearlyPlan lastYearlyPlan = spYearlyPlanMapper.selectOne(
                new LambdaQueryWrapper<SpYearlyPlan>()
                        .eq(SpYearlyPlan::getYear,year-1)
                        .last("limit 1")
        );
        List<SpYearPlanSchemeDetail> lastDetails = new LinkedList<>();
        if(lastYearlyPlan != null){
            SpYearPlanScheme lastYearPlanScheme = spYearPlanSchemeMapper.selectOne(
                    new LambdaQueryWrapper<SpYearPlanScheme>()
                            .eq(SpYearPlanScheme::getPlanId,lastYearlyPlan.getId())
                            .eq(SpYearPlanScheme::getIsHistory,0)
                            .eq(SpYearPlanScheme::getIsShare,1)
                            .orderByDesc(SpYearPlanScheme::getCreateDate)
                            .last("limit 1")
            );
            if(lastYearPlanScheme != null){
                lastDetails = spYearPlanSchemeDetailService.getBaseMapper().selectList(
                        new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                                .eq(SpYearPlanSchemeDetail::getSchemeId,lastYearPlanScheme.getId())
                                .ge(SpYearPlanSchemeDetail::getExpectedEmptyReturnWindow,year+"-01-01")
                );
            }
        }

        if(dto.getConId()!=null){
            ConResourceMemorandum resource = resourceMemorandumMapper.getById(dto.getConId());
            if(resource!=null){
                details = details.stream().filter(d->d.getSeller().equals(resource.getSeller())).collect(Collectors.toList());
                lastDetails = lastDetails.stream().filter(d->d.getSeller().equals(resource.getSeller())).collect(Collectors.toList());
            }
        }
        if(details.size() == 0){
            emptyResultData.put("firstShipLoadingTime", com.ship.common.core.utils.DateUtil.String2Date(year+"-01-01 00:00:00","yyyy-MM-dd").getTime());
            return JsonResult.success(emptyResultData);
        }
        details = details.stream().sorted(Comparator.comparing(SpYearPlanSchemeDetail::getSchemeNumber)).collect(Collectors.toList());

        for (SpYearPlanSchemeDetail detail : details) {
            if(detail.getExpectedLoadingPortId() != null){
                List<String> portIds = new LinkedList<>();
                portIds.add(String.valueOf(detail.getExpectedLoadingPortId()));
                detail.setExpectedLoadingPorts(portIds);
            }
            if(detail.getExpectedUnloadingPortId() != null){
                List<String> portIds = new LinkedList<>();
                portIds.add(String.valueOf(detail.getExpectedUnloadingPortId()));
                detail.setExpectedUnloadingPorts(portIds);
            }
        }

        // 获取第一艘船装载时间和卸载时间
        SpYearPlanSchemeDetail detailFirst = details.stream().min(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
        if(lastDetails.size() > 0){
            detailFirst = lastDetails.stream().min(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
        }

        JSONObject resultData = new JSONObject();
        resultData.put("firstShipLoadingTime",DateUtil.offsetMonth(detailFirst.getExpectedLoadingDate(),-2).getTime());

        //船舶排布
        List<SpYearPlanSchemeDetail> boatSchemeList = new LinkedList<>();
        boatSchemeList.addAll(details);
        boatSchemeList.addAll(lastDetails);
        Map<String,List<SpYearPlanSchemeDetail>> boatMap = boatSchemeList.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipName));
        JSONArray boatsArray = new JSONArray();

        BigDecimal shipDays = BigDecimal.ZERO;
        if(planState != 3){
            for (SpInformation spInformation : spInformations) {
                String boatName = spInformation.getNameOfVessel();
                JSONObject boatDetailJson = new JSONObject();
                List<SpYearPlanSchemeDetail> boatDetails = boatMap.get(boatName) != null ? boatMap.get(boatName) : new LinkedList<>();
                if(boatDetails.size() > 0 && boatDetails.get(0).getDispatchCost() != null){
                    dispatchCost = dispatchCost.add(boatDetails.get(0).getDispatchCost());
                }
                for (int i = 0; i < boatDetails.size(); i++) {
                    SpYearPlanSchemeDetail boatDetail = boatDetails.get(i);
                    if(boatDetail.getExpectedFullLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedFullLoadVoyageDays())));
                    }
                    if(boatDetail.getExpectedEmptyLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedEmptyLoadVoyageDays())));
                    }
                    if(boatDetail.getTradingProfit() != null){
                        tradingProfit = tradingProfit.add(boatDetail.getTradingProfit());
                    }
                    if(boatDetail.getFrontDays() != null && boatDetail.getFrontDays() > 30){
                        emptyDays = (emptyDays + boatDetail.getFrontDays());
                    }
                    if(i == (boatDetails.size() - 1)){
                        if(boatDetail.getBackDays() != null && boatDetail.getBackDays() > 30){
                            emptyDays = (emptyDays + boatDetail.getBackDays());
                        }
                    }
                }
                if(boatDetails.size() == 0 && boatName.indexOf("虚拟") > -1){
                    continue;
                }
                boatDetailJson.put("imo",spInformation.getImo());
                boatDetailJson.put("storageCapacity",spInformation.getStorageCapacity());
                boatDetailJson.put("storageCapacityMmbtu",spInformation.getStorageCapacityMmbtu());
                boatDetailJson.put("rentTimeSlot",JSONArray.parseArray(spInformation.getRentTimeSlot()));
                boatDetailJson.put("shipName",boatName);
                boatDetailJson.put("details",boatDetails);
                boatsArray.add(boatDetailJson);
            }
        }else{
            for (SpInformation spInformation : spInformations) {
                String boatName = spInformation.getNameOfVessel();
                if(boatMap.get(boatName) == null){
                    continue;
                }
                JSONObject boatDetailJson = new JSONObject();
                List<SpYearPlanSchemeDetail> boatDetails = boatMap.get(boatName) != null ? boatMap.get(boatName) : new LinkedList<>();
                if(boatDetails.size() > 0 && boatDetails.get(0).getDispatchCost() != null){
                    dispatchCost = dispatchCost.add(boatDetails.get(0).getDispatchCost());
                }
                for (int i = 0; i < boatDetails.size(); i++) {
                    SpYearPlanSchemeDetail boatDetail = boatDetails.get(i);
                    if(boatDetail.getExpectedFullLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedFullLoadVoyageDays())));
                    }
                    if(boatDetail.getExpectedEmptyLoadVoyageDays() != null){
                        shipDays = shipDays.add(new BigDecimal(String.valueOf(boatDetail.getExpectedEmptyLoadVoyageDays())));
                    }
                    if(boatDetail.getTradingProfit() != null){
                        tradingProfit = tradingProfit.add(boatDetail.getTradingProfit());
                    }
                    if(boatDetail.getFrontDays() != null && boatDetail.getFrontDays() > 30){
                        emptyDays = (emptyDays + boatDetail.getFrontDays());
                    }
                    if(i == (boatDetails.size() - 1)){
                        if(boatDetail.getBackDays() != null && boatDetail.getBackDays() > 30){
                            emptyDays = (emptyDays + boatDetail.getBackDays());
                        }
                    }
                }
                if(boatDetails.size() == 0 && boatName.indexOf("虚拟") > -1){
                    continue;
                }
                boatDetailJson.put("imo",spInformation.getImo());
                boatDetailJson.put("rentTimeSlot",JSONArray.parseArray(spInformation.getRentTimeSlot()));
                boatDetailJson.put("shipName",boatName);
                boatDetailJson.put("storageCapacity",spInformation.getStorageCapacity());
                boatDetailJson.put("storageCapacityMmbtu",spInformation.getStorageCapacityMmbtu());
                boatDetailJson.put("details",boatDetails);
                boatsArray.add(boatDetailJson);
            }
        }

        resultData.put("shipNames",boatsArray);
        resultData.put("unoccupiedDays",emptyDays);

        //资源排布
        Map<String,List<SpYearPlanSchemeDetail>> goodsMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getGoodsNum));
        List<JSONObject> goodsArray = new LinkedList<>();
        for (String goodsName : goodsMap.keySet()) {
            JSONObject goodsDetailJson = new JSONObject();
            goodsDetailJson.put("goodsName",goodsName);
            goodsDetailJson.put("number",goodsMap.get(goodsName).get(0).getSchemeNumber());
            goodsDetailJson.put("details",goodsMap.get(goodsName));
            goodsArray.add(goodsDetailJson);
        }
        goodsArray = goodsArray.stream().sorted(
                Comparator.comparingInt(a -> ((JSONObject)a).getIntValue("number"))).collect(Collectors.toList());
        resultData.put("goodsNames",goodsArray);
        //资源方排布
        Map<String,List<SpYearPlanSchemeDetail>> sourceSideMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getSeller));
        JSONArray sourceSideArray = new JSONArray();
        for (String sourceSideName : sourceSideMap.keySet()) {
            List<SpYearPlanSchemeDetail> detailList = sourceSideMap.get(sourceSideName);
            JSONObject sourceSideDetailJson = new JSONObject();
            sourceSideDetailJson.put("sellerName",sourceSideName);
            sourceSideDetailJson.put("details",detailList);
            //获取资源方该年度的计划货量
            SpYearPlanCapacityRecord capacityRecord = getSellerPlanCapacity(detailList.get(0).getPlanId(),sourceSideName);
            if(capacityRecord != null){
                sourceSideDetailJson.put("planGoodsCapacity",capacityRecord.getPlanGoodsCapacity());
                sourceSideDetailJson.put("recordId",capacityRecord.getRecordId());
            }
            sourceSideArray.add(sourceSideDetailJson);
        }
        //船舶利用率=（总的航行天数（空载+满载）+租出的天数）/年度租入天数
        shipDays = shipDays.add(yearOutRentDays);

        if(yearInRentDays.compareTo(BigDecimal.ZERO) > 0){
            shipUseRate = shipDays.multiply(new BigDecimal("100")).divide(yearInRentDays, 2, RoundingMode.HALF_UP);
        }

        resultData.put("sellerNames",sourceSideArray);
        resultData.put("planId",spYearPlanScheme.getPlanId());
        resultData.put("schemeId",spYearPlanScheme.getId());
        resultData.put("isDistribute",spYearPlanScheme.getIsDistribute());
        resultData.put("shipUseRate",shipUseRate);
        resultData.put("dispatchCost",dispatchCost);
        resultData.put("tradingProfit",tradingProfit);
        return JsonResult.success(resultData);
    }

    /**
     * 获取资源方该年度的计划货量
     * @param planId
     * @param seller
     * @return
     */
    public SpYearPlanCapacityRecord getSellerPlanCapacity(Long planId, String seller){
        SpYearPlanCapacityRecord capacityRecord = spYearPlanCapacityRecordService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpYearPlanCapacityRecord>()
                        .eq(SpYearPlanCapacityRecord::getPlanId,planId)
                        .eq(SpYearPlanCapacityRecord::getSeller,seller)
                        .last("limit 1")
        );
        return capacityRecord;
    }

    @Override
    public SpYearPlanSchemeDetailBO updateSpYearPlanSchemeDetail(SpYearPlanSchemeDetailBO planSchemeDetail){
        SpYearPlanSchemeDetail schemeDetail = spYearPlanSchemeDetailService.getBaseMapper().selectById(planSchemeDetail.getId());
        if(schemeDetail == null){
            throw new BaseException("方案信息异常！");
        }
        Boolean isUpdate = false;
        if(planSchemeDetail.getExpectedLoadingPorts() != null && planSchemeDetail.getExpectedUnloadingPorts() != null
                && schemeDetail.getExpectedLoadingPortId() != null && schemeDetail.getExpectedUnloadingPortId() != null){
            if(!planSchemeDetail.getExpectedLoadingPorts().contains(schemeDetail.getExpectedLoadingPortId())
                    || !planSchemeDetail.getExpectedUnloadingPorts().contains(schemeDetail.getExpectedUnloadingPortId())){
                isUpdate = true;
            }
        }

        BeanCopyUtils.copy(planSchemeDetail,schemeDetail);

        if(schemeDetail.getExpectedLoadingPorts() != null && schemeDetail.getExpectedLoadingPorts().size() > 0
                && schemeDetail.getExpectedUnloadingPorts() != null && schemeDetail.getExpectedUnloadingPorts().size() > 0){
            List<Long> startPortList = schemeDetail.getExpectedLoadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            List<Long> endPortList = schemeDetail.getExpectedUnloadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo = spPortRouteDistanceService.getCommonLoadDay(startPortList
                    ,endPortList,schemeDetail.getShipSpeed(),schemeDetail.getPassStrait(),schemeDetail.getPassArea());

            if(shipLoadDaysAndRouteVo == null){
                throw new BaseException("暂无航线信息！");
            }

            //装载港
            schemeDetail.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
            schemeDetail.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
            //卸载港
            schemeDetail.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
            schemeDetail.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
            schemeDetail.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
            schemeDetail.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
            schemeDetail.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
            schemeDetail.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
            schemeDetail.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

            int shipDays = 0;
            int chinaDays = 0;
            int europeDays = 0;
            int indiaDays = 0;

            if (shipLoadDaysAndRouteVo.getShipDay() != null) {
                shipDays = shipLoadDaysAndRouteVo.getShipDay();
            }
            if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
                chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
            }
            if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
                europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
            }
            if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
                indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
            }

            Date expectLoadDate = schemeDetail.getExpectedLoadingDate();
            schemeDetail.setExpectedLoadingDate(expectLoadDate);
            schemeDetail.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

            //预计卸载时间
            schemeDetail.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
            //中国预计卸载时间
            schemeDetail.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
            //印度预计卸载时间
            schemeDetail.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
            //欧洲预计卸载时间
            schemeDetail.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
            //预计满载航程天数
            schemeDetail.setExpectedFullLoadVoyageDays(shipDays);
            //印度空载航程天数
            schemeDetail.setIndiaEmptyLoadDays(indiaDays);
            //欧洲空载航程天数
            schemeDetail.setEuropeEmptyLoadDays(europeDays);
            //预计卸载完成窗口
            schemeDetail.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(), 2));

            //中国预计空载返程窗口
            schemeDetail.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
            //欧洲预计空载返程窗口
            schemeDetail.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
            //印度预计空载返程窗口
            schemeDetail.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

            //预计空载航程天数
            SpYearPlanSchemeDetail lastSchemeDetail = spYearPlanSchemeDetailService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                            .lt(SpYearPlanSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                            .eq(SpYearPlanSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                            .eq(SpYearPlanSchemeDetail::getImo,schemeDetail.getImo())
                            .orderByDesc(SpYearPlanSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            schemeDetail.setLastShipSpeed(schemeDetail.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());

                //没有空载时间则清空空载航线信息
                schemeDetail.setEmptyDistance(0);
                schemeDetail.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,schemeDetail.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    schemeDetail.setEmptyDistance(routeDay.getTrackDistance());
                    schemeDetail.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    schemeDetail.setExpectedEmptyLoadVoyageDays(day);
                    schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    schemeDetail.setEmptyDistance(0);
                    schemeDetail.setLastUnloadPortName("");
                    schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                    schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }
        }

        spYearPlanSchemeDetailService.getBaseMapper().updateById(schemeDetail);

        if(isUpdate){
            List<SpYearPlanSchemeDetail> yearPlanSchemeDetails = detailMapper.selectList(
                    new LambdaQueryWrapper<SpYearPlanSchemeDetail>().eq(SpYearPlanSchemeDetail::getSchemeId, schemeDetail.getSchemeId()));
            if (yearPlanSchemeDetails.size() > 0) {
                yearPlanSchemeDetails = CommonUtil.resetVoyageNoAndGoodsNumber(yearPlanSchemeDetails);

                //重新计算空载时间
                yearPlanSchemeDetails = resetSetLastEmptyInfo(yearPlanSchemeDetails);


                Calendar calendar = Calendar.getInstance();
                calendar.setTime(schemeDetail.getExpectedLoadingDate());
                int year = calendar.get(Calendar.YEAR);

                //重新计算一下前后空挡信息
                yearPlanSchemeDetails = getIntervalDays(yearPlanSchemeDetails,year);

                //计算优化成本及船舶利用率
                yearPlanSchemeDetails = getUseRateAndDispatchCost(yearPlanSchemeDetails,year);

                spYearPlanSchemeDetailService.updateBatchById(yearPlanSchemeDetails);
            }
        }

        return new SpYearPlanSchemeDetailBO();
    }

    @Override
    public JsonResult schemeChartCalculateSave(SchemeChartSaveDto dto) {
        List<SpYearPlanSchemeDetail> oriDetails = dto.getSchemeSaveData();
        if(oriDetails.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }
        List<SpYearPlanSchemeDetail> details = new LinkedList<>();
        for (SpYearPlanSchemeDetail oriDetail : oriDetails) {
            if(oriDetail.getSchemeId().longValue() == dto.getSchemeId()){
                details.add(oriDetail);
            }
        }

        //校验每艘船设置的货运时间是否有冲突
        Map<String,List<SpYearPlanSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipName));
        for (String boatName : boatMap.keySet()) {
            List<SpYearPlanSchemeDetail> boatSchemeDetailList = boatMap.get(boatName);
            //先根据装载时间排序，然后遍历判断，如果当前数据的装载时间时间在上一条数据的返程时间之前，则表示时间窗口有冲突
            boatSchemeDetailList = boatSchemeDetailList.stream().sorted(Comparator.comparing(SpYearPlanSchemeDetail::getExpectedLoadingDate)).collect(Collectors.toList());
            Date compareReturnDate = null;
            for (SpYearPlanSchemeDetail detail : boatSchemeDetailList) {
                if(compareReturnDate != null && detail.getExpectedLoadingDate().before(compareReturnDate)){
                    return JsonResult.failed("时间窗口冲突【船名："+boatName+",装载时间："+detail.getExpectedLoadingDate()+"】");
                }
                compareReturnDate = detail.getExpectedEmptyReturnWindow();
            }
        }

        //获取原始方案数据
        SpYearPlanScheme spYearPlanScheme = spYearPlanSchemeMapper.selectById(dto.getSchemeId());
        if(spYearPlanScheme == null){
            return JsonResult.failed("方案信息异常");
        }
        if(spYearPlanScheme.getIsDistribute() != null && spYearPlanScheme.getIsDistribute().intValue() == 1){
            return JsonResult.failed("当前方案已下发,操作失败!");
        }

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumber(details);

        for (int i = 1; i <= details.size(); i++) {
            SpYearPlanSchemeDetail detail = details.get(i-1);
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }
            //计算时间空挡天数以及贸易利润率信息
            getIntervalDaysAndTradeRate(detail,spInformation,null);
        }

        SpYearlyPlan spYearlyPlan = spYearlyPlanMapper.selectById(spYearPlanScheme.getPlanId());
        if(spYearlyPlan == null){
            return JsonResult.failed("计划信息异常!");
        }

        if(details.size() > 0){
            spYearPlanSchemeDetailService.updateBatchById(details);
        }

        //重新计算空载时间
        details = resetSetLastEmptyInfo(details);

        //重新计算一下前后空挡信息
        details = getIntervalDays(details,spYearlyPlan.getYear());

        //计算优化成本及船舶利用率
        details = getUseRateAndDispatchCost(details,spYearlyPlan.getYear());
        if(details.size() > 0){
            spYearPlanSchemeDetailService.updateBatchById(details);
        }

        return JsonResult.success();
    }

    @Override
    public JsonResult schemeChartSave(SchemeChartSaveDto dto) {
        List<SpYearPlanSchemeDetail> oriDetails = dto.getSchemeSaveData();
        if(oriDetails.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }
        List<SpYearPlanSchemeDetail> details = new LinkedList<>();
        for (SpYearPlanSchemeDetail oriDetail : oriDetails) {
            if(oriDetail.getSchemeId().longValue() == dto.getSchemeId()){
                details.add(oriDetail);
            }
        }

        //校验每艘船设置的货运时间是否有冲突
        Map<String,List<SpYearPlanSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipName));
        for (String boatName : boatMap.keySet()) {
            List<SpYearPlanSchemeDetail> boatSchemeDetailList = boatMap.get(boatName);
            //先根据装载时间排序，然后遍历判断，如果当前数据的装载时间时间在上一条数据的返程时间之前，则表示时间窗口有冲突
            boatSchemeDetailList = boatSchemeDetailList.stream().sorted(Comparator.comparing(SpYearPlanSchemeDetail::getExpectedLoadingDate)).collect(Collectors.toList());
            Date compareReturnDate = null;
            for (SpYearPlanSchemeDetail detail : boatSchemeDetailList) {
                if(compareReturnDate != null && detail.getExpectedLoadingDate().before(compareReturnDate)){
                    return JsonResult.failed("时间窗口冲突【船名："+boatName+",装载时间："+detail.getExpectedLoadingDate()+"】");
                }
                compareReturnDate = detail.getExpectedEmptyReturnWindow();
            }
        }

        //获取原始方案数据
        SpYearPlanScheme spYearPlanScheme = spYearPlanSchemeMapper.selectById(dto.getSchemeId());
        if(spYearPlanScheme == null){
            return JsonResult.failed("方案信息异常");
        }
        if(spYearPlanScheme.getIsDistribute() != null && spYearPlanScheme.getIsDistribute().intValue() == 1){
            return JsonResult.failed("当前方案已下发,操作失败!");
        }

        if(dto.getSaveType().intValue() == 3){
            //下发需要审批，当前有未审批信息的时候不能再次发起
            if(0 == spYearPlanScheme.getApproveStatus()){
                throw new ServiceException("当前方案还有未审批的下发，请审批后再发布！", BaseResultCode.GENERAL_ERROR.getCode());
            }
        }

        Integer number = null;
        if(dto.getSaveType().intValue() != 1){
            SpYearPlanScheme maxScheme = spYearPlanSchemeMapper.selectOne(
                    new LambdaQueryWrapper<SpYearPlanScheme>()
                            .eq(SpYearPlanScheme::getIsShare,1)
                            .eq(SpYearPlanScheme::getPlanId,spYearPlanScheme.getPlanId())
                            .isNotNull(SpYearPlanScheme::getNumber)
                            .orderByDesc(SpYearPlanScheme::getNumber)
                            .last("limit 1")
            );
            number = 1;
            if (maxScheme != null) {
                number = (maxScheme.getNumber() + 1);
            }

            //如果不是个人草稿,则将之前的所有记录都设置为历史记录,当前共享的数据为最新的一条
            SpYearPlanScheme updateData = new SpYearPlanScheme();
            updateData.setIsHistory(1);
            spYearPlanSchemeMapper.update(updateData,new LambdaQueryWrapper<SpYearPlanScheme>().eq(SpYearPlanScheme::getPlanId,spYearPlanScheme.getPlanId()));
        }

        //新增一条方案信息
        spYearPlanScheme.setId(null);
        spYearPlanScheme.setIsHistory(0);
        spYearPlanScheme.setIsShare(0);
        spYearPlanScheme.setNumber(number);
        if(dto.getSaveType().intValue() != 1){
            spYearPlanScheme.setIsShare(1);
        }
        if(dto.getSaveType().intValue() == 3){
            //需要审批
            spYearPlanScheme.setIsDistribute(0);
            spYearPlanScheme.setApproveStatus(0);
        }
        spYearPlanScheme.setCreateDate(new Date());
        spYearPlanScheme.setCreateBy(SecurityUtils.getUsername());
        spYearPlanSchemeMapper.insert(spYearPlanScheme);

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumber(details);

        for (int i = 1; i <= details.size(); i++) {
            SpYearPlanSchemeDetail detail = details.get(i-1);
            Long id = detail.getId();
            detail.setSchemeId(spYearPlanScheme.getId());
            detail.setCreateDate(new Date());
            detail.setLastDetailId(id);
            detail.setCreateBy(SecurityUtils.getUsername());
            detail.setId(null);
            if(dto.getSaveType().intValue() == 3){
                //如果是已下发，则将方案数据船和时间都锁定，状态为已下发
                detail.setLockTime(0);
                detail.setLockShip(0);
                detail.setIsDistribute(0);
            }
            detail.setLoadingCapacity(detail.getLoadingCapacity().divide(ROUND_NUMBER,0,RoundingMode.DOWN).multiply(new BigDecimal("10000")));
            detail.setSingleGoodsQuantity(detail.getSingleGoodsQuantity().divide(ROUND_NUMBER,0,RoundingMode.DOWN).multiply(new BigDecimal("10000")));
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }
            //计算时间空挡天数以及贸易利润率信息
            getIntervalDaysAndTradeRate(detail,spInformation,null);
        }

        SpYearlyPlan spYearlyPlan = spYearlyPlanMapper.selectById(spYearPlanScheme.getPlanId());
        if(spYearlyPlan == null){
            return JsonResult.failed("计划信息异常!");
        }

        if(details.size() > 0){
            spYearPlanSchemeDetailService.saveBatch(details);
        }

        //重新计算空载时间
        details = resetSetLastEmptyInfo(details);

        //重新计算一下前后空挡信息
        details = getIntervalDays(details,spYearlyPlan.getYear());

        //计算优化成本及船舶利用率
        details = getUseRateAndDispatchCost(details,spYearlyPlan.getYear());
        if(details.size() > 0){
            spYearPlanSchemeDetailService.updateBatchById(details);
        }

        if(dto.getSaveType().intValue() == 3){
            ProcessInstanceParam param = new ProcessInstanceParam();
            param.setBpmKey(bpmInstanceService.SP_YEAR_PLAN_BPM_KEY);
            param.setBussinessId(spYearPlanScheme.getPlanId());
            Map<String,Object> map = new HashMap<>();
            map.put("moduleName","年度计划");
            map.put("reason","");
            param.setParams(map);
            bpmInstanceService.startProcessInstance(param);
            return JsonResult.success("方案发布已提交审批！");
        }

        if(dto.getSaveType().intValue() != 1){
            //方案下发,修改年度计划信息
            //将对应的年度计划改成已生效

            spYearlyPlan.setState(dto.getSaveType().intValue() == 3 ? 3 : 1);

            // 资源方数量
            Map<String, List<SpYearPlanSchemeDetail>> collect = details.stream().collect(Collectors
                    .groupingBy(SpYearPlanSchemeDetail::getSeller));
            Set<String> longs = collect.keySet();
            spYearlyPlan.setResourceSide(longs.size());

            //计划货船数
            spYearlyPlan.setPlannedNumberOfShipments(details.size());

            // 计划装载量
            BigDecimal reduce = details.stream().map(SpYearPlanSchemeDetail::getSingleGoodsQuantity)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            spYearlyPlan.setPlannedLoadingCapacity(reduce);

            // 获取第一艘船装载时间和卸载时间
            SpYearPlanSchemeDetail detailFirst = details.stream().min(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
            // 获取最后一艘船的装载时间和卸载时间
            SpYearPlanSchemeDetail detailLast = details.stream().max(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();

            spYearlyPlan.setFirstTimeLoading(detailFirst.getExpectedLoadingDate());
            spYearlyPlan.setFirstTimeUnloading(detailFirst.getExpectedUnloadingCompletionWindow());
            spYearlyPlan.setLastTimeLoading(detailLast.getExpectedLoadingDate());
            spYearlyPlan.setLastTimeUnloading(detailLast.getExpectedUnloadingCompletionWindow());

            spYearlyPlanMapper.updateById(spYearlyPlan);

            //共享和下发都需要更新班期表
            shipScheduleService.updateShipSchedule(spYearPlanScheme.getId());
        }

        return JsonResult.success();
    }

    @Override
    public JsonResult<SpYearPlanSchemeDetail> tradingProfitUpdate(SpYearPlanSchemeDetail detail) {
        if(detail.getId() == null){
            return JsonResult.failed("id不能为空");
        }
        SpYearPlanSchemeDetail schemeDetail = spYearPlanSchemeDetailService.getById(detail.getId());
        if(schemeDetail == null){
            return JsonResult.failed("方案详情信息异常");
        }
        BeanCopyUtils.copy(detail,schemeDetail);
        BigDecimal defUnitValue = new BigDecimal("10000");
        //预计采购成本 计划装载量*预计采购单价
        schemeDetail.setConPriceCost(schemeDetail.getConPrice().multiply(schemeDetail.getSingleGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
        //实际采购成本 实际装载量*实际采购单价
        BigDecimal realityConPriceCost = schemeDetail.getRealityConPrice().multiply(schemeDetail.getSingleGoodsQuantity());
        schemeDetail.setRealityConPriceCost(realityConPriceCost.divide(defUnitValue,2,RoundingMode.UP));
        //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
        schemeDetail.setRealityLoadTotalCost((realityConPriceCost.add(schemeDetail.getLoadPassCost())
                .add(schemeDetail.getLoadPortCost()).add(schemeDetail.getLoadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));

        //预计销售收入
        schemeDetail.setExpectSellMoney(schemeDetail.getSellPrice().multiply(schemeDetail.getExpectedUnloadGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
        //实际销售收入
        BigDecimal realitySellMoney = schemeDetail.getRealitySellPrice().multiply(schemeDetail.getRealityUnloadingCapacity());
        schemeDetail.setRealitySellMoney(realitySellMoney.divide(defUnitValue,2,RoundingMode.UP));

        //国际贸易利润=船舶优化成本+实际销售收入+其他收入-装载运河、港口、其他费用-卸载运河、港口、其他费用-实际采购成本  单位万美元
        schemeDetail.setNationalTradingProfit((realitySellMoney.add(schemeDetail.getOtherGetMoney()).subtract(schemeDetail.getLoadPassCost())
                .subtract(schemeDetail.getLoadPortCost()).subtract(schemeDetail.getLoadOtherCost())
                .subtract(schemeDetail.getUnloadPassCost()).subtract(schemeDetail.getUnloadPortCost())
                .subtract(schemeDetail.getUnloadOtherCost()).subtract(realityConPriceCost)).divide(defUnitValue,2,RoundingMode.UP));

        //实际总交付成本=运河费（卸载）+港口费（卸载）+其他费用（卸载)
        schemeDetail.setRealityTotalDeliverCost((schemeDetail.getUnloadPassCost()
                .add(schemeDetail.getUnloadPortCost()).add(schemeDetail.getUnloadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));

        //获取船只租金租金信息
//        ASFDASD

        //贸易利润率=国际贸易利润+补货利润+船舶优化成本
        if(schemeDetail.getRestockProfit() == null){
            schemeDetail.setRestockProfit(BigDecimal.ZERO);
        }
        if(schemeDetail.getDispatchCost() == null){
            schemeDetail.setDispatchCost(BigDecimal.ZERO);
        }
        schemeDetail.setTradingProfit(schemeDetail.getNationalTradingProfit().add(schemeDetail.getRestockProfit()).add(schemeDetail.getDispatchCost()));
        spYearPlanSchemeDetailService.updateById(schemeDetail);
        return JsonResult.success(schemeDetail);
    }

    /**
     * 保存资源方装载货量信息
     * @param planId
     * @param year
     * @param resourceMemorandum
     */
    public void saveSellerCapacityRecord(Long planId, int year, ConResourceMemorandum resourceMemorandum){
        String seller = resourceMemorandum.getSeller();
        BigDecimal loadCapacity = resourceMemorandum.getLoadingOrUnloadingCapacity();
        //根据年份获取上一年度的年度计划
        SpYearlyPlan lastYearPlan = spYearlyPlanMapper.selectOne(
                new LambdaQueryWrapper<SpYearlyPlan>()
                .eq(SpYearlyPlan::getYear,year-1)
                .last("limit 1")
        );
        spYearPlanCapacityRecordService.saveSellerCapacityRecord(seller,planId,lastYearPlan,loadCapacity);
    }


    /**
     * 获取贸易利润率及空挡天数
     * @param schemeDetail
     * @param shipInfo
     * @param memorandum
     */
    private void getIntervalDaysAndTradeRate(SpYearPlanSchemeDetail schemeDetail
            ,SpInformation shipInfo,ConResourceMemorandum memorandum){
        if(memorandum == null && schemeDetail.getResourceContract() != null){
            memorandum = resourceMemorandumMapper.selectById(schemeDetail.getResourceContract());
        }
        //实际交付量 = 预计装载量-预计装载量*（时间差天数）*蒸发率-留底量 如果是虚拟船就默认等于预计装载量
        BigDecimal realityUnload = schemeDetail.getSingleGoodsQuantity();
        //蒸发率
        schemeDetail.setEvaporationRate(shipInfo.getEvaporationRate() != null ? shipInfo.getEvaporationRate() : new BigDecimal("0.1"));
        BigDecimal subDec = BigDecimal.ZERO;
        if(shipInfo.getEvaporationRate() != null){
            subDec = schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getEvaporationRate()).divide(new BigDecimal("100"));
        }
        //蒸发量
        schemeDetail.setExpectedEvaporationQuantity(subDec);

//        if(shipInfo.getRedeliveryStatus() != null){
//            subDec = subDec.add(shipInfo.getRedeliveryStatus());
//        }
        realityUnload = schemeDetail.getSingleGoodsQuantity().subtract(subDec);

        //预计卸载量
        schemeDetail.setExpectedUnloadGoodsQuantity(realityUnload);
        //实际卸载量
        schemeDetail.setRealityUnloadingCapacity(realityUnload);

        //涉及成本计算的字段单位都是万美元
        BigDecimal defUnitValue = new BigDecimal("10000");
        BigDecimal defZeroValue = BigDecimal.ZERO;

        schemeDetail.setLoadPassCost(defZeroValue);
        schemeDetail.setLoadPortCost(defZeroValue);
        schemeDetail.setLoadOtherCost(defZeroValue);
        schemeDetail.setUnloadPassCost(defZeroValue);
        schemeDetail.setUnloadPortCost(defZeroValue);
        schemeDetail.setUnloadOtherCost(defZeroValue);
        schemeDetail.setExpectedBottomQuantity(defZeroValue);

        //实际交付价格 = 合同价格
        if(memorandum != null && memorandum.getContractPrice() != null){
            //预计采购价格
            schemeDetail.setConPrice(memorandum.getContractPrice());
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(schemeDetail.getConPrice().multiply(schemeDetail.getSingleGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(schemeDetail.getConPrice());
            //实际采购成本 实际装载量*实际采购单价
            BigDecimal realityConPriceCost = schemeDetail.getRealityConPrice().multiply(schemeDetail.getSingleGoodsQuantity());
            schemeDetail.setRealityConPriceCost(realityConPriceCost.divide(defUnitValue,2,RoundingMode.UP));
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost((realityConPriceCost.add(schemeDetail.getLoadPassCost())
                    .add(schemeDetail.getLoadPortCost()).add(schemeDetail.getLoadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        }else{
            schemeDetail.setConPrice(defZeroValue);
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(defZeroValue);
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(defZeroValue);
            //实际采购成本 实际装载量*实际采购单价
            schemeDetail.setRealityConPriceCost(defZeroValue);
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost(defZeroValue);
        }

        /**
         * 年度计划默认都是回国，根据贸易利润为0来推算国内销售价格
         * 回国：贸易利润 = 实际交付量（预计卸载量）*实际交付价格（实际销售的价格）
         * +其他收入（为0）-其他交付成本（为0）-实际装载量（预计装载量）*实际结算单价（长协合同里面的合同价格）-其他费用（为0）
         */

        //国际贸易利润
        schemeDetail.setNationalTradingProfit(defZeroValue);
        BigDecimal sellPrice = BigDecimal.ZERO;
        if(schemeDetail.getConPrice().compareTo(BigDecimal.ZERO) > 0){
            sellPrice = (schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getConPrice()))
                    .divide(schemeDetail.getRealityUnloadingCapacity(),2,RoundingMode.HALF_UP);
        }
        //预计销售价格
        schemeDetail.setSellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setExpectSellMoney(schemeDetail.getSellPrice().multiply(schemeDetail.getExpectedUnloadGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
        //实际销售价格
        schemeDetail.setRealitySellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setRealitySellMoney(schemeDetail.getRealitySellPrice().multiply(schemeDetail.getRealityUnloadingCapacity()).divide(defUnitValue,2,RoundingMode.UP));
        //其他收入
        schemeDetail.setOtherGetMoney(defZeroValue);
        //补货利润
        schemeDetail.setRestockProfit(defZeroValue);
        //船舶优化成本=外租收益-（优化运力成本-基准运力成本）
        schemeDetail.setDispatchCost(defZeroValue);
        //船舶利用率（这个指标是全局的所以单船货的不需要计算）
        schemeDetail.setShipUseRate(defZeroValue);
        //可优化天数
        schemeDetail.setUnoccupiedDays(0);
        //实际总交付成本=运河费（卸载）+港口费（卸载）+其他费用（卸载)
        schemeDetail.setRealityTotalDeliverCost((schemeDetail.getUnloadPassCost()
                .add(schemeDetail.getUnloadPortCost()).add(schemeDetail.getUnloadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        //贸易利润率=国际贸易利润+补货利润+船舶优化成本
        schemeDetail.setTradingProfit(schemeDetail.getNationalTradingProfit().add(schemeDetail.getRestockProfit()).add(schemeDetail.getDispatchCost()));
    }

    /**
     * 虚拟船排货逻辑
     * @param unUseLoadTime
     * @param virtualShips
     * @param schemeModel
     * @param goodsQuantity
     * @param goodsNumber
     * @param planId
     * @param year
     * @param resource
     * @param schemeId
     */
    public void virtualShipDispatch(List<Date> unUseLoadTime,List<SpInformation> virtualShips,SpYearPlanSchemeModel schemeModel
            ,BigDecimal goodsQuantity,Integer goodsNumber,Long planId, int year, ConResourceMemorandum resource
            , Long schemeId, Boolean isEnd, ShipLoadDaysAndRouteVo loadDays){
        BigDecimal virGoodsQuantity = goodsQuantity;
        Integer virGoodsNumber = goodsNumber;
        List<Date> virUnUseLoadTime = new LinkedList<>();
        for (Date loadDate : unUseLoadTime) {
            if (virGoodsQuantity != null) {
                if(virGoodsQuantity.compareTo(BigDecimal.ZERO) <= 0){
                    //剩余货量为0，插入结束
                    break;
                }
            } else {
                if(virGoodsNumber <= 0){
                    //剩余货量为0，插入结束
                    break;
                }
            }

            //根据装载时间初步生成船货信息
            SpYearPlanSchemeDetail schemeDetail = initPlanSchemeDetail(year,resource,schemeId,loadDate,schemeModel, loadDays);
            schemeDetail.setPlanId(planId);

            SpInformation virtualShip = null;
            for (SpInformation spInformation : virtualShips) {
                if(checkShipTimeWindows(spInformation,schemeDetail)){
                    virtualShip = spInformation;
                    break;
                }
            }
            if(virtualShip == null){
                virUnUseLoadTime.add(loadDate);
                continue;
            }

            // 本艘船能够装载的货量（百万英热），排货默认满载
            BigDecimal mut = virtualShip.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                    .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                    .setScale(0, BigDecimal.ROUND_DOWN);

            if (virGoodsQuantity != null) {
                if(goodsQuantity.compareTo(mut) > 0){
                    if(virGoodsQuantity.compareTo(mut) < 0){
                        //如果剩余货量达不到当前船只满载，则舍去这船货
                        virGoodsQuantity = BigDecimal.ZERO;
                        break;
                    }
                }else{
                    //输入货量小于舱容，则说明只拉一船货，装载量=货量
                    mut = virGoodsQuantity;
                }
                virGoodsQuantity = virGoodsQuantity.subtract(mut);
            } else {
                if(virGoodsNumber == 0){
                    //货数已拍完
                    virGoodsNumber = 0;
                    return;
                }
                virGoodsNumber = (virGoodsNumber -1);
            }

            mut = mut.divide(ROUND_NUMBER,0,RoundingMode.DOWN).multiply(new BigDecimal("10000"));
            schemeDetail.setShipName(virtualShip.getNameOfVessel());
            schemeDetail.setImo(virtualShip.getImo());
            schemeDetail.setSingleGoodsQuantity(mut);
            schemeDetail.setLoadingCapacity(mut);
            schemeDetail.setOilConsumption(virtualShip.getOilConsumption());
            schemeDetail.setStorageCapacity(virtualShip.getStorageCapacity());
            //计算时间空挡天数以及贸易利润率信息
            getIntervalDaysAndTradeRate(schemeDetail,virtualShip,resource);

            spYearPlanSchemeDetailService.getBaseMapper().insert(schemeDetail);

            updateShipSchedule(schemeDetail,virtualShip);
        }

//        if(virUnUseLoadTime.size() > 0 && !isEnd){
//            if (virGoodsQuantity != null) {
//                if(virGoodsQuantity.compareTo(BigDecimal.ZERO) > 0){
//                    //所有时间派完了都还有剩余，说明船只不够，使用虚拟船拉获取现新增虚拟船
//                    SpInformation addVirtualShip = spInformationService.createVirtualSpInformation();
//                    List<SpInformation> ships = new LinkedList<>();
//                    ships.add(addVirtualShip);
//                    virtualShipDispatch(virUnUseLoadTime,ships,schemeModel
//                            ,virGoodsQuantity,null,planId, year, resource, schemeId, true,loadDays);
//                }
//            } else {
//                if(virGoodsNumber > 0){
//                    //所有时间派完了都还有剩余，说明船只不够，使用虚拟船拉获取现新增虚拟船
//                    SpInformation addVirtualShip = spInformationService.createVirtualSpInformation();
//                    List<SpInformation> ships = new LinkedList<>();
//                    ships.add(addVirtualShip);
//                    virtualShipDispatch(virUnUseLoadTime,ships,schemeModel
//                            ,null,virGoodsNumber,planId, year, resource, schemeId,true,loadDays);
//                }
//            }
//        }
    }

    /**
     * 校验船只时间窗口是否符合
     * @param ship
     * @param schemeDetail
     * @return
     */
    public Boolean checkShipTimeWindows(SpInformation ship,SpYearPlanSchemeDetail schemeDetail){
        Boolean isSuccess = false;

        //默认空载时间等于满载时间来计算
        schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~schemeDetail.getExpectedFullLoadVoyageDays() + 1));

        //解决时间跨度的问题
        Date startDate = DateUtil.offsetDay(schemeDetail.getExpectedEmptyReturnWindow(),-1);
        Date endDate = DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(),1);

        //当前船只已排货时间信息
        Long count = spYearPlanSchemeDetailService.getBaseMapper().selectCount(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                .eq(SpYearPlanSchemeDetail::getImo,ship.getImo())
                .eq(SpYearPlanSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                .and(qw -> {
                    qw.between(SpYearPlanSchemeDetail::getExpectedUnloadingWindow,startDate,endDate);
                    qw.or().between(SpYearPlanSchemeDetail::getExpectedEmptyReturnWindow,startDate,endDate);
                })
        );
        if(count > 0){
            return isSuccess;
        }

        Long countOut = spYearPlanSchemeDetailService.getBaseMapper().selectCount(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .eq(SpYearPlanSchemeDetail::getImo,ship.getImo())
                        .eq(SpYearPlanSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .le(SpYearPlanSchemeDetail::getExpectedEmptyReturnWindow,schemeDetail.getExpectedEmptyReturnWindow())
                        .ge(SpYearPlanSchemeDetail::getExpectedUnloadingWindow,schemeDetail.getExpectedUnloadingWindow())
        );
        if(countOut > 0){
            return isSuccess;
        }

        //校验上一船货船期是否满足
        //预计空载航程天数
        SpYearPlanSchemeDetail lastSchemeDetail = spYearPlanSchemeDetailService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .lt(SpYearPlanSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpYearPlanSchemeDetail::getImo,schemeDetail.getImo())
                        .eq(SpYearPlanSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .orderByDesc(SpYearPlanSchemeDetail::getExpectedLoadingDate)
                        .last("limit 1")
        );
        if(lastSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                    ,schemeDetail.getExpectedLoadThirdId(),null,null);
            if(routeDay == null){
                //与上一船货卸载港无航线，则说明到不了，不能排货
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date currentEmptyDate = DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(currentEmptyDate.before(lastSchemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        //校验当前船只下一船货船期是否满足
        SpYearPlanSchemeDetail nextSchemeDetail = spYearPlanSchemeDetailService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .gt(SpYearPlanSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpYearPlanSchemeDetail::getImo,ship.getImo())
                        .eq(SpYearPlanSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .last("limit 1")
        );
        if(nextSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),schemeDetail.getExpectedUnloadThirdId()
                    ,nextSchemeDetail.getExpectedLoadThirdId(),null,null);
            if(routeDay == null){
                //与下一船货装载港无航线，则说明到不了，不能排货
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date nextEmptyDate = DateUtil.offsetDay(nextSchemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(nextEmptyDate.before(schemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        if(ship.getIsVirtual().intValue() == 1){
            //虚拟船不需要校验船期
            return true;
        }

        //判断是否在出租期内
        List<ConShipping> rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .eq(ConShipping::getContractType,3)
        );
        for (ConShipping conShipping : rentShip) {
            //空载时间和装载时间在租期内，则表示已租出去，不能使用
            if ((schemeDetail.getExpectedEmptyReturnWindow().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedEmptyReturnWindow().after(conShipping.getDeliveryDateBegin()))
                    || (schemeDetail.getExpectedUnloadingWindow().after(conShipping.getDeliveryDateBegin())
                    && schemeDetail.getExpectedUnloadingWindow().before(conShipping.getRedeliveryDateEnd()))) {
                return isSuccess;
            }
        }

        //是否处于OFF-HIRE区间
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .eq(ConShipping::getStatus,5)
        );
        for (ConShipping conShipping : rentShip) {
            if(StringUtils.isBlank(conShipping.getHireTimeValue())){
                continue;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                List<HireTimeDetailsBO> hireTimeDetailsBOS = objectMapper.readValue(conShipping.getHireTimeValue()
                        , new TypeReference<List<HireTimeDetailsBO>>() {});
                if(hireTimeDetailsBOS.size() == 0){
                    continue;
                }
                for (HireTimeDetailsBO hireTimeDetailsBO : hireTimeDetailsBOS) {
                    Boolean inStatus = (hireTimeDetailsBO.getStart().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getEnd().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedUnloadingWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedEmptyReturnWindow()));
                    if(inStatus){
                        return isSuccess;
                    }
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        //是否在租入时间内
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .in(ConShipping::getContractType,1,2)
        );
        for (ConShipping conShipping : rentShip) {
            if (schemeDetail.getExpectedEmptyReturnWindow().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedEmptyReturnWindow().after(conShipping.getDeliveryDateBegin())) {
                schemeDetail.setShipContractId(conShipping.getId());
                schemeDetail.setShipContractName(conShipping.getContractName());
                schemeDetail.setShipTradeBody(conShipping.getTradeBody());
                schemeDetail.setShipContractType(conShipping.getContractType());
                return true;
            }
        }
        return false;
    }

    /**
     * 更新船期表
     * @param schemeDetail
     */
    public void updateShipSchedule(SpYearPlanSchemeDetail schemeDetail,SpInformation loadShipInfo){
        if(loadShipInfo.getImo().indexOf("virtualShip") > -1 && StringUtils.isBlank(loadShipInfo.getRentTimeSlot())){
            //虚拟船租金信息为空，说明是刚新增的，需要根据装卸载时间来进行租期设置
            Date startDate = schemeDetail.getExpectedUnloadingWindow();
            Date endDate = schemeDetail.getExpectedLoadingDate();

            Calendar calendar1 = Calendar.getInstance();
            calendar1.setTime(startDate);
            Calendar calendar2 = Calendar.getInstance();
            calendar2.setTime(endDate);
            if(calendar1.get(Calendar.YEAR) != calendar2.get(Calendar.YEAR)){
                //说明跨年了，租期结束时间得推到卸载时间得年末
                calendar2.set(Calendar.MONTH, Calendar.DECEMBER);
                calendar2.set(Calendar.DAY_OF_MONTH, 31);
                endDate = calendar2.getTime();
            }

            //组装租期、租金信息
            JSONArray array = new JSONArray();
            JSONObject json = new JSONObject();
            json.put("rentDateBegin",startDate);
            json.put("rentDateEnd",endDate);
            json.put("rentParagraph",6000);
            json.put("rentUnit",1);
            array.add(json);
            loadShipInfo.setRentTimeSlot(array.toJSONString());
            spInformationService.getBaseMapper().updateById(loadShipInfo);
        }


        //使用这个变量来判断这艘船是否再租出时间范围内，如果再就为true
        AtomicReference<Boolean> judge = new AtomicReference<>(false);
        //起租开始时间
        AtomicReference<Date> startingDateOfLeaseBegin = new AtomicReference<>();
        //还船结束时间
        AtomicReference<Date> redeliveryDateEnd = new AtomicReference<>();
        //租出时间，多条
        List<String> leaseTermStrs = new ArrayList<>();
        //从船运合同中获取信息
        List<ConShipping> conShippings = conShippingMapper.getByIMO(loadShipInfo.getImo());
        if (conShippings != null && conShippings.size() > 0) {
            conShippings.stream().forEach(c -> {
                if (c.getContractType() != null && c.getContractType().equals(3)) {
                    //租出
                    leaseTermStrs.add(DateUtil.format(c.getDeliveryDateBegin(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(c.getDeliveryDateEnd(), "yyyy-MM-dd HH:mm:ss"));
                    //空载时间小于交船时间
                    if (c.getDeliveryDateBegin()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(c.getDeliveryDateBegin())) {
                        judge.set(true);
                        //装载时间大于还船时间
                        if (c.getDeliveryDateEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(c.getDeliveryDateEnd())) {
                            judge.set(true);
                        }else{
                            judge.set(false);
                        }
                    }
                } else {
                    schemeDetail.setShipContractId(c.getId());
                    schemeDetail.setShipContractName(c.getContractName());
                    schemeDetail.setShipTradeBody(c.getTradeBody());
                    schemeDetail.setShipContractType(c.getContractType());
                    if (c.getDeliveryDateBegin() != null) {
                        startingDateOfLeaseBegin.set(c.getDeliveryDateBegin());
                    }
                    redeliveryDateEnd.set(c.getRedeliveryDateEnd());
                }
                //off-hire状态，时间段不能生成货数.并且记录在船期表中
                if(c.getStatus()!=null && c.getStatus().equals(5)){
                    if(StringUtils.isNotBlank(c.getHireTimeValue())){
                        List<HireTimeDetailsBO> hireTimeDetailsBOS = new ArrayList<>();
                        ObjectMapper objectMapper = new ObjectMapper();
                        try {
                            hireTimeDetailsBOS = objectMapper.readValue(c.getHireTimeValue(), new TypeReference<List<HireTimeDetailsBO>>() {
                            });
                            if(hireTimeDetailsBOS!=null && hireTimeDetailsBOS.size()>0){
                                hireTimeDetailsBOS.stream().forEach(h->{
                                    //空载时间小于交船时间
                                    if (h.getStart()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(h.getStart())) {
                                        judge.set(true);
                                        //装载时间大于还船时间
                                        if (h.getEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(h.getEnd())) {
                                            judge.set(true);
                                        }else{
                                            judge.set(false);
                                        }
                                    }
                                    leaseTermStrs.add(DateUtil.format(h.getStart(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(h.getEnd(), "yyyy-MM-dd HH:mm:ss"));
                                });
                            }
                        } catch (JsonProcessingException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            });
        }

        //保存船舶班期表
        SpShipSchedule spShipSchedule = new SpShipSchedule();
        BeanUtils.copyProperties(schemeDetail, spShipSchedule);
        spShipSchedule.setId(IDUtil.nextId());
        BigDecimal goodsQuantityNew = loadShipInfo.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                .setScale(0, BigDecimal.ROUND_DOWN);
        //生成年度计划，每船货上下浮动3.25%
        BigDecimal storageCapacityFloat = goodsQuantityNew.multiply(BigDecimal.valueOf(0.0325))
        .setScale(0, BigDecimal.ROUND_DOWN);
        BigDecimal maxStorageCapacity = goodsQuantityNew.add(storageCapacityFloat);
        BigDecimal minStorageCapacity = goodsQuantityNew.subtract(storageCapacityFloat);
        spShipSchedule.setMaxStorageCapacity(maxStorageCapacity);
        spShipSchedule.setMinStorageCapacity(minStorageCapacity);
        spShipSchedule.setStorageCapacity(goodsQuantityNew);
        spShipSchedule.setExecuteStatus(0);
        if (startingDateOfLeaseBegin.get() != null) {
            spShipSchedule.setStartingDateOfLeaseBegin(startingDateOfLeaseBegin.get());
        }
        if (redeliveryDateEnd.get() != null) {
            spShipSchedule.setRedeliveryDateEnd(redeliveryDateEnd.get());
        }
        if (leaseTermStrs != null && leaseTermStrs.size() > 0) {
            spShipSchedule.setLeaseTermStr(leaseTermStrs.toString());
        }
        shipScheduleService.saveShipSchedule(spShipSchedule);
    }

    /**
     * 根据资源方和年份获取装载时间在当前年份内方案详情信息
     * @param seller
     * @param year
     * @param schemeId
     * @return
     */
    public List<Date> getSchemeDetailsBySellerAndYear(List<Integer> loadMonth,Long schemeId, String seller,int year){
        // 获取指定年份的第一天
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, Calendar.JANUARY);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        Date firstDay = calendar.getTime();

        // 获取指定年份的最后一天
        calendar.set(Calendar.MONTH, Calendar.DECEMBER);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        Date lastDay = calendar.getTime();

        //装载时间在当前年度内的方案详情信息
        List<SpYearPlanSchemeDetail> schemeDetails = spYearPlanSchemeDetailService.getBaseMapper().selectList(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                .eq(SpYearPlanSchemeDetail::getSeller,seller)
                .eq(SpYearPlanSchemeDetail::getSchemeId,schemeId)
                .ge(SpYearPlanSchemeDetail::getExpectedLoadingDate,firstDay)
                .le(SpYearPlanSchemeDetail::getExpectedLoadingDate,lastDay)
        );
        List<Date> schemeDateList = schemeDetails.stream().map(SpYearPlanSchemeDetail::getExpectedLoadingDate).collect(Collectors.toList());
        SpYearPlanSchemeDetail lastYearDetail = spYearPlanSchemeDetailService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .eq(SpYearPlanSchemeDetail::getSeller,seller)
                        .eq(SpYearPlanSchemeDetail::getIsDistribute,1)
                        .le(SpYearPlanSchemeDetail::getExpectedLoadingDate,firstDay)
                        .orderByDesc(SpYearPlanSchemeDetail::getExpectedLoadingDate)
                        .last("limit 1")
        );
        schemeDateList = schemeDateList.stream().sorted().collect(Collectors.toList());
        List<Date> resultList = new LinkedList<>();
        if(schemeDateList.size() == 0){
            //如果没有排过货，则从头开始间隔40天的方式来进行排货
            Date beginTime = firstDay;
            if(lastYearDetail != null && com.ship.common.core.utils.DateUtil.getDateDays(lastYearDetail.getExpectedLoadingDate(),firstDay,1) < 35){
                beginTime = DateUtil.offsetDay(lastYearDetail.getExpectedLoadingDate(),35);
            }
            resultList.add(beginTime);
            while (beginTime.before(lastDay)){
                beginTime = DateUtil.offsetDay(beginTime,40);
                if(beginTime.after(lastDay)){
                    //超出了年尾，跳出时间筛选
                    break;
                }
                resultList.add(beginTime);
            }
        }else{
            //排过货则根据已排货物之间的间隔时间来进行插货
            for (int i = 0; i < schemeDateList.size(); i++) {
                Date schemeDate = schemeDateList.get(i);

                if(i == 0){
                    //如果是第一个时间，需要校验年头的时间间隔
                    Date yearBeginTime = firstDay;
                    if(lastYearDetail != null){
                        yearBeginTime = DateUtil.offsetDay(lastYearDetail.getExpectedLoadingDate(),35);
                    }
                    if(com.ship.common.core.utils.DateUtil.getDateDays(yearBeginTime,schemeDate,1) >= 35){
                        //当前年度内年头时间据下一次的装载时间超过35天才有判断排货的意义
                        resultList.add(yearBeginTime);
                        while (yearBeginTime.before(schemeDate) && com.ship.common.core.utils.DateUtil.getDateDays(yearBeginTime,schemeDate,1) >= 80){
                            yearBeginTime = DateUtil.offsetDay(yearBeginTime,40);
                            resultList.add(yearBeginTime);
                        }
                    }
                }
                if(i == schemeDateList.size()-1){
                    //如果是最后一个时间，需要校验年尾的时间间隔
                    Date yearEndTime = schemeDate;
                    while (yearEndTime.before(lastDay)){
                        yearEndTime = DateUtil.offsetDay(yearEndTime,40);
                        if(yearEndTime.after(lastDay)){
                            //超出了年尾，跳出时间筛选
                            break;
                        }
                        resultList.add(yearEndTime);
                    }
                    break;
                }

                if(com.ship.common.core.utils.DateUtil.getDateDays(schemeDate,schemeDateList.get(i+1),1) < 80){
                    //间隔时间不足以插一船货
                    continue;
                }
                Date beginTime = DateUtil.offsetDay(schemeDate,40);
                resultList.add(beginTime);
                while (com.ship.common.core.utils.DateUtil.getDateDays(beginTime,schemeDateList.get(i+1),1) >= 80){
                    beginTime = DateUtil.offsetDay(beginTime,40);
                    resultList.add(beginTime);
                }
            }
        }
        resultList = resultList.stream().sorted().collect(Collectors.toList());

        if(loadMonth != null && loadMonth.size() > 0){
            //如果选择了月份，则需要优先排选择月份的货并且要保证选择的每个月都能排一船货，先根据选定的装载月份来生成装载时间，在反推年头和年尾
            List<Date> selectDateList = new LinkedList<>();
            Date currentMonthDate = null;
            loadMonth = loadMonth.stream().sorted().collect(Collectors.toList());
            for (Integer integer : loadMonth) {
                calendar.set(Calendar.MONTH, integer-1);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                Date monthDate = calendar.getTime();
                if(currentMonthDate == null){
                    currentMonthDate = monthDate;
                    selectDateList.add(monthDate);
                    continue;
                }
                if(com.ship.common.core.utils.DateUtil.getDateDays(currentMonthDate,monthDate,1) < 40){
                    //周期不满足40天间隔，根据上一次时间重新生成
                    monthDate = DateUtil.offsetDay(currentMonthDate,40);
                    currentMonthDate = monthDate;
                    selectDateList.add(monthDate);
                    continue;
                }
                selectDateList.add(monthDate);
            }

            List<Date> finalDateList = new LinkedList<>();
            finalDateList.addAll(selectDateList);

            Date monthBeginDate = resultList.get(0);
            Date monthEndDate = resultList.get(resultList.size()-1);
            //根据现有时间间隔来反推其他月份的时间
            for (int i = 0; i < selectDateList.size(); i++) {
                Date schemeDate = selectDateList.get(i);

                if(i == 0){
                    //如果是第一个时间，需要校验年头的时间间隔
                    Date yearBeginTime = monthBeginDate;
                    if(com.ship.common.core.utils.DateUtil.getDateDays(yearBeginTime,schemeDate,1) >= 35){
                        //当前年度内年头时间据下一次的装载时间超过35天才有判断排货的意义
                        finalDateList.add(yearBeginTime);
                        while (yearBeginTime.before(schemeDate) && com.ship.common.core.utils.DateUtil.getDateDays(yearBeginTime,schemeDate,1) >= 80){
                            yearBeginTime = DateUtil.offsetDay(yearBeginTime,40);
                            finalDateList.add(yearBeginTime);
                        }
                    }
                }
                if(i == selectDateList.size()-1){
                    //如果是最后一个时间，需要校验年尾的时间间隔
                    Date yearEndTime = schemeDate;
                    while (yearEndTime.before(monthEndDate)){
                        yearEndTime = DateUtil.offsetDay(yearEndTime,40);
                        if(yearEndTime.after(monthEndDate)){
                            //超出了年尾，跳出时间筛选
                            break;
                        }
                        finalDateList.add(yearEndTime);
                    }
                    break;
                }

                if(com.ship.common.core.utils.DateUtil.getDateDays(schemeDate,selectDateList.get(i+1),1) < 80){
                    //间隔时间不足以插一船货
                    continue;
                }
                Date monthBeginTime = DateUtil.offsetDay(schemeDate,40);
                finalDateList.add(monthBeginTime);
                while (com.ship.common.core.utils.DateUtil.getDateDays(monthBeginTime,selectDateList.get(i+1),1) >= 80){
                    monthBeginTime = DateUtil.offsetDay(monthBeginTime,40);
                    finalDateList.add(monthBeginTime);
                }
            }
            return finalDateList;
        }

        return resultList;
    }

    /**
     * 初始化方案详情信息
     * @param year
     * @param resource
     * @param schemeId
     * @param expectLoadDate
     * @param schemeModel
     * @return
     */
    public SpYearPlanSchemeDetail initPlanSchemeDetail(int year, ConResourceMemorandum resource
            , Long schemeId, Date expectLoadDate, SpYearPlanSchemeModel schemeModel, ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo){
        SpYearPlanSchemeDetail schemeDetail = new SpYearPlanSchemeDetail();
        //装载港
        schemeDetail.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
        schemeDetail.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
        //卸载港
        schemeDetail.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
        schemeDetail.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
        schemeDetail.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
        schemeDetail.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
        schemeDetail.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
        schemeDetail.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
        schemeDetail.setShipSpeed(schemeModel.getSpeed());
        schemeDetail.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

        int shipDays = 0;
        int chinaDays = 0;
        int europeDays = 0;
        int indiaDays = 0;

        if (shipLoadDaysAndRouteVo.getShipDay() != null) {
            shipDays = shipLoadDaysAndRouteVo.getShipDay();
        }
        if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
            chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
        }
        if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
            europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
        }
        if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
            indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
        }
        schemeDetail.setCreateBy(SecurityUtils.getUsername());
        schemeDetail.setId(IDUtil.nextId());
        schemeDetail.setSchemeId(schemeId);
        schemeDetail.setSchemeNumber(1);

        // 从资源合同服务获取资源方信息
        if (resource != null) {
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            schemeDetail.setLoadingCapacityUnit(resource.getUnit());
            schemeDetail.setResourceContract(schemeModel.getConId());
            if (resource.getContractName() != null) {
                schemeDetail.setContractName(resource.getContractName());
            }
            schemeDetail.setSeller(resource.getSeller());
            if (resource.getTradeType() != null) {
                schemeDetail.setTradeType(resource.getTradeType());
            }
            if (resource.getTradeBody() != null) {
                schemeDetail.setTradeBody(resource.getTradeBody());
            }
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            if (resource.getBuyer() != null) {
                schemeDetail.setBuyer(resource.getBuyer());
            }
        }

        schemeDetail.setVoyageNo("CNOOC" + year + "" + "01");
        schemeDetail.setGoodsNum(schemeDetail.getSeller() + "FOB" + "01");
        //换港时间 1:合同中的换港时间约定，BP，窗口前 75 天，马石油中期，窗口所在月份第一天的前 60 天
        //换港时间 2:合同中的换港时间约定，BP，窗口前 60 天，马石油中期，窗口前 60 天
        if (StringUtils.isNotBlank(resource.getChangePortTimeLoading())) {
            Date changePortTimeLoading = DateUtil.parse(resource.getChangePortTimeLoading(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -75));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -60));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            }
        }

        //换船/指船时间 1:合同中的约定，BP，窗口前 45 天，马石油中期，窗口所在月份第一天的前 20 天
        //换船时间 2:合同中的约定，BP，窗口前 25 天，马石油中期，窗口前 20 天
        if (StringUtils.isNotBlank(resource.getChangeShipTime())) {
            Date changeShipTime = DateUtil.parse(resource.getChangeShipTime(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -45));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -25));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -20));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -20));
            }
        }

        schemeDetail.setExpectedLoadingDate(expectLoadDate);
        schemeDetail.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

        //预计卸载时间
        schemeDetail.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
        //中国预计卸载时间
        schemeDetail.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
        //印度预计卸载时间
        schemeDetail.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
        //欧洲预计卸载时间
        schemeDetail.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
        //预计满载航程天数
        schemeDetail.setExpectedFullLoadVoyageDays(shipDays);
        //印度空载航程天数
        schemeDetail.setIndiaEmptyLoadDays(indiaDays);
        //欧洲空载航程天数
        schemeDetail.setEuropeEmptyLoadDays(europeDays);
        //预计卸载完成窗口
        schemeDetail.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(), 2));

        //中国预计空载返程窗口
        schemeDetail.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
        //欧洲预计空载返程窗口
        schemeDetail.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
        //印度预计空载返程窗口
        schemeDetail.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

        schemeDetail.setCreateDate(new Date());
        schemeDetail.setShipSpeed(schemeModel.getSpeed());
        schemeDetail.setQuantityUnit(schemeModel.getQuantityUnit());
        //TODO:巴拿马运河预定时间:装载时间提前30天 写死，后面从字典表中获取
        schemeDetail.setPanamaCanalReservationDate(DateUtil.offsetDay(expectLoadDate, -30));
        return schemeDetail;
    }

    @Override
    public void update(SpYearPlanSchemeDetail detail) {
        SpDispatchSchemeDetail spDispatchSchemeDetail = dispatchDetailMapper.selectById(detail.getId());
        BeanUtil.copyProperties(detail, spDispatchSchemeDetail);
        dispatchDetailMapper.updateById(spDispatchSchemeDetail);
    }

    /**
     * 查询资源合同
     */
    @Override
    public List<ConResourceMemorandum> selectAllContractNumbers(Integer tradeModel) {
        return resourceMemorandumMapper.getAllContractNumbers(tradeModel);
    }

    /**
     * 查询资源方
     */
    @Override
    public List<ConResourceSellerVO> selectAllSellers() {
        List<ConResourceSellerVO> sellerVOS = new ArrayList<>();
        List<ConResourceMemorandum> resources = resourceMemorandumMapper.getAllContractNumbers(1);
        if (resources != null && resources.size() > 0) {
            resources.stream().forEach(r -> {
                ConResourceSellerVO sellerVO = new ConResourceSellerVO();
                sellerVO.setId(r.getId());
                sellerVO.setSeller(r.getSeller());
                sellerVOS.add(sellerVO);
            });
        }
        return sellerVOS;
    }

    /**
     * 根据合同编号查出资源方（卖方）
     */
    @Override
    public String getContractNumberBySeller(String contractNumber) {
        return resourceMemorandumMapper.getContractNumberBySeller(contractNumber);
    }

    @Override
    public void saveBase64Image(String base64Data, String fileName) {
        // 移除数据开头的描述信息，获取实际的 Base64 编码数据
        String[] parts = base64Data.split(",");
        String base64Image = parts.length > 1 ? parts[1] : base64Data;

        // 将 Base64 数据解码为字节数组
        byte[] decodedBytes = Base64.getDecoder().decode(base64Image);

        // 保存解码后的字节数组为图片文件
        try (OutputStream outputStream = new FileOutputStream(fileName)) {
            outputStream.write(decodedBytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
